One Command to a Running App
A setup guide with twenty steps is a list of ways to fail. I've started treating 'clone, run one command, see the app' as a feature of every codebase. Here's what goes into that command, and the design choices that make it possible.
6 min read
Every codebase has a first-day experience. For most, it looks like a setup document: install this, copy that file, ask someone for these keys, start the database, run the migrations, hope the versions match. Each step is reasonable. Together, they're a list of ways to fail.
On a recent project, the setup document had grown to the usual shape. Install the dependencies. Export a database URL by hand. Run migrations through a specific package filter. Log in to the cloud provider, pick the right account and pull secrets. Start the dev server with the one bundler that worked. Every step was correct, and every step was a place where someone could get stuck.
So I replaced it with one command.
pnpm run setup
Clone the repo, run that, and you're looking at the app. This post is about what that command does, and more importantly the design choices that make a single command possible at all.
What the command does#
The setup script walks through the same steps the old document described, but it checks before it acts and explains what it's doing.
- Check the runtime. Confirm the right version of Node, and turn on the package manager through the built-in tooling if it isn't already there.
- Install dependencies for the whole monorepo.
- Create the local environment file from the example template, if one doesn't exist yet. It never overwrites an existing one.
- Get secrets without copying them. If there's no cloud login on the machine, it opens a browser to sign in, then pulls the development secrets from the cloud secret manager into a gitignored local file. Nobody pastes a key into chat.
- Fill the gaps it can't fill itself. If a third-party key is still missing, it opens that service's dashboard to the right page and tells you what to paste.
- Start a database if there isn't one. If nothing is listening on the expected port, it starts a local Postgres in a container.
- Run migrations, and sync the environment into the app that needs it.
Add a flag and it starts the app at the end. Add a different flag and it skips the cloud step entirely, for someone who just wants to look around.
None of these steps is clever. The value is that they run in order, every time, with checks between them.
The design choices that make it possible#
A setup script can only be simple if the app is designed to start in imperfect conditions. That's the real work. A few choices did most of it.
Run with nothing configured#
The app boots and stays useful with no external services at all.
- No database configured? Every data store has an in-memory implementation behind the same interface. A tiny accessor checks whether a database is configured and quietly falls back if not.
- No notification service keys? Notification calls no-op and log, instead of throwing.
- No auth keys? Public pages still load. Sign-in is the only thing that needs keys.
I call this "fail soft for setup, fail closed for safety". Missing infrastructure makes features quieter, never less safe. A missing database means data doesn't persist between restarts. It never means a permission check gets skipped. (The safety side of that is in Fail Closed, Capture Before You Act.)
The important part is consistency. Every service uses the same pattern: a single "is this configured?" check and a graceful fallback. So "runs with nothing configured" is a property of the codebase, not a happy accident in a few places.
Demo data that's one click away#
An empty app teaches nobody anything. A local seed script fills the app with realistic sample organizations and decisions, and there's also a small button in the development build that does the same thing. It's guarded so it can only run in development, behind an explicit opt-in.
New people see the product working in minutes, instead of clicking through empty states and wondering what it's supposed to look like.
Secrets live in one place#
Development secrets live in the cloud secret manager, and nowhere else. The setup script pulls them through a normal browser login. There are no shared key files, no secrets in the repo and no "can someone send me the env file?".
That's a security property as much as a convenience. The two most common ways credentials leak are a secret committed to git and a key file sitting on a laptop. A setup flow that never asks anyone to handle a raw key removes both.
Gates you run locally#
The same command family includes the checks that protect the architecture:
- One gate fails if any feature code reaches around the scoped database client and touches tables directly.
- One gate fails if packages start depending on each other in cycles.
- One gate fails if ported files are missing the header that records where they came from.
They run the same way locally and in CI. A new contributor finds out about the house rules from a clear failure on their own machine, not from a review comment three days later.
A small naming trap#
One detail I enjoyed: the package manager has its own built-in command called setup, which does something completely different. Running the bare command does nothing for the project and is very confusing.
So the README says, in a callout: use pnpm run setup, with run. It's a tiny thing, and it would have cost every new person ten minutes of confusion. Good onboarding is mostly a collection of small things like this, found by watching someone hit them.
Why this matters more now#
I used to think of setup as a one-time cost, paid once per new hire. It isn't anymore.
I spin up fresh environments constantly: a clean clone to test a change, a new worktree for a parallel task, a sandbox where an agent works on its own branch. Every one of those is a "first day". If setup takes thirty minutes and two Slack messages, I won't do it, and I'll take shortcuts instead. If it takes one command, I'll do it every time.
Agents feel this even more. An agent working in a fresh checkout can run one well-defined command and get a working app. It can't ask a teammate for an env file. A setup flow that needs a human to fill gaps is a setup flow agents can't use. I wrote more about structuring codebases for that kind of work in The Consolidation of Code Over Time.
How I'd add this to an existing project#
You don't need a rewrite to get here. In order:
- Write down the current setup steps, honestly. Every one, including the ones people only learn by asking.
- Make the app boot without each external service, one at a time. In-memory fallbacks for data, no-ops for notifications, public pages without auth.
- Move secrets to one managed home, with a login-based pull.
- Script the steps in order, checking before each one and never overwriting local changes.
- Add a seed so the first screen isn't empty.
- Watch a new person run it, and fix whatever confuses them.
Then delete the long setup document, or shrink it to a few lines that point at the command.
The takeaway#
"Clone, run one command, see the app" isn't a script. It's an architecture: an app that starts in imperfect conditions, secrets in one place and checks that run the same everywhere. Once you have it, every fresh environment is cheap, for new teammates, for parallel work and for agents that can't ask anyone for help.
Related
- Engineering
The Consolidation of Code Over Time
Codebases don't stay sprawling forever. Given enough years, they converge or they die. What one product's repos taught me about monorepos, deleting more than you add, and why the best month of a codebase can be the month it gets smaller.
Architecture · Codebases · TypeScript · Leadership7 min read
- Engineering
Don't Install Yesterday's Release
Most malicious package releases are caught within hours. A one-day waiting period before installing anything new costs almost nothing and sidesteps a whole class of supply-chain attacks. Here's how I set it up, and where the exceptions go.
Security · Infrastructure · Next.js6 min read
- Product
Shipping a Real Product in 24 Hours
Why I still build products against a one-day clock, and the habits that make it work: cutting scope hard, using the same stack every time, letting agents do the heavy lifting, leaning on a design system, and agreeing on what 'done' means before I start.
Product · Prototyping · Agents · Design systems8 min read