Start Any Platform From Scratch
ForkTex Engineering · September 5, 2026 · 3 min
A new engineer joins on a Monday. How long before they have the system running on their machine, with a database, seeded, hot-reloading, and able to see the effect of a change they just made?
If the answer involves a wiki page, the answer is wrong. Because the wiki page describes the setup as it was when someone last had a bad enough day to write it down.
Our answer is two commands.
make deps
make startThat is the same on every project we run. Not similar. The same.
Declared, not assembled
The stack is not a hand-written compose file. It is declared: which services exist, which images they run, which ports they expose, which volumes mount, what each depends on. A tool compiles that declaration into a running local stack.
The distinction matters more than it first appears. A hand-written compose file is a second description of the system, maintained in parallel with the production one and drifting from it continuously. The local database is a version behind. An environment variable that production sets is missing locally, so a code path that reads it is never exercised until it is exercised by a customer.
When both environments compile from one declaration, that class of difference stops being possible. Local and production differ where they are *declared* to differ, mounted source, a mail catcher instead of a real SMTP relay, observability off, and nowhere else.
Hot reload is a property of the environment, not the editor
Source directories mount into the running containers. Editing a file on the host reloads the process inside the container. No rebuild, no restart, no image push.
Two details are what make this pleasant rather than fragile.
The first is that dependency directories are *not* mounted from the host. A node_modules installed on the host and mounted into a Linux container is a reliable way to spend an afternoon on a native binary compiled for the wrong platform. It lives in a named volume owned by the container instead. The host's copy, if there is one, exists only to make the editor's language server happy.
The second is that migrations run on startup. The API container applies pending migrations before it serves. There is no state where the code expects a column the local database has not got, because bringing the stack up is what reconciles them.
Reset must be cheap
make db-resetWipes the volume, restarts, re-applies migrations. Back to a known state in seconds.
This sounds like a convenience. It is closer to a prerequisite for good work. When resetting is expensive, engineers avoid it. They patch broken local state by hand, accumulate a database that resembles no other database anywhere, and eventually hit a bug that reproduces only for them. When resetting is cheap, the correct response to confusing local state is to throw it away, and everyone's environment converges rather than drifts.
The check that keeps it true
Documentation about how to run a system decays because nothing tests it. Ours is verified by the fact that it is the only path: there is no alternative way to start the stack that a team member might use instead and thereby stop noticing that the documented one broke.
The honest caveat is that this holds as long as the tooling is available and current. A tool that has moved on while a repository's instructions have not is the same stale-wiki problem wearing better clothes. Version floors are declared for that reason, and a mismatched version fails with a message that says so, rather than failing halfway through with something inscrutable.
Why it is worth the effort
The obvious return is onboarding: a new engineer contributes on day one instead of day three.
The larger return is that it removes an entire category of argument. Nobody debates whether a failure is environmental. Nobody maintains a private setup script. Nobody is the one person who can reproduce the bug. The environment stops being a variable, and the only thing left to discuss is the code. Which is the conversation worth having.