Skip to content
Engineering/local-development

Your laptop should mirror production

"We maintain local environments that match production to prevent configuration drift."

Why this exists

Local development setups decay silently. The 'works on my machine' bug is almost always caused by environment drift, such as different Node versions or mocked services that behave incorrectly. If a new developer cannot spin up a working local environment in under two hours, our documentation is broken.

Operational Flow

1

Document: Maintain a `DEVELOPMENT.md` file in the repository root with the exact commands needed to run the app. We verify these steps on a clean machine once per quarter.

2

Lock versions: Specify exact runtime versions in `.nvmrc` or `.tool-versions`. Version discrepancies introduce bugs that are invisible until you deploy.

3

Declare variables: List all required environment variables in `.env.example` with clear descriptions. Missing variables must fail loudly during startup.

4

Containerize services: Run databases, queues, and caches via Docker Compose using the exact versions running in production. If production uses PostgreSQL 15, local must run PostgreSQL 15.

5

Seed data: Provide a script that populates the local database with realistic mock data. Running `pnpm db:seed` must prepare the app for manual testing.

6

Verify: Use `pnpm dev:check` to verify that environment variables are set and all local services are accessible before reporting local errors.

7

Keep in sync: Update `docker-compose.yml` and `DEVELOPMENT.md` in the same pull request that updates production dependencies.

What good looks like

  • A new developer follows `DEVELOPMENT.md` on a clean machine, starts the application in 90 minutes, and opens their first pull request on day one.
  • An engineer pulls the latest changes after a two-week vacation and is running the app within five minutes.
  • You can reproduce a production error locally on your first attempt because the data shapes and service versions match.

What NOT to do

  • Do not rely on verbal instructions for local setup. If a step is missing from `DEVELOPMENT.md`, it does not exist.
  • Do not use SQLite or a different database version locally. SQL dialects and locking behaviors vary.
  • Do not commit production secrets or local API keys to the repository. Use `.env.example` instead.
  • Do not allow setup documentation to drift. Outdated setup guides waste hours of developer time.

Local development environments decay when ignored. Services get updated in production while the local container configurations stay outdated. A new environment variable gets added without updating the example configuration. These omissions are invisible to current developers but stop new team members immediately. We test our setup process by handing a clean laptop to someone unfamiliar with the codebase. The app should run in under two hours.

A database with no data makes debugging difficult. You cannot test UI states, debug query plans, or reproduce production issues on an empty system. We maintain an idempotent seed script using anonymized production data. A realistic seed script is a highly effective piece of engineering infrastructure, but it is often the last thing teams build.