Local Dev Environments: The Setup That Keeps a Team Unblocked
Nobody puts a local dev environment on a résumé, and yet it decides how fast everything else goes. A new developer joins, runs one command, and has a working app in twenty minutes — or spends two days discovering that the setup guide is from 2022 and references a PHP version nobody has installed. We've been on both sides. Here's the setup we standardize on and the habits that keep it from rotting.
Containerize the services, not necessarily the app
Our default for Laravel + Vue projects: run PHP and Node natively for fast test feedback and readable stack traces, and put everything stateful or fussy into Docker Compose — Postgres/MySQL, Redis, a queue worker if it matters for the task, and a local mail catcher like Mailpit. You get version parity with production without paying container startup costs on every test run.
services:
db:
image: postgres:17-alpine
environment:
POSTGRES_PASSWORD: secret
ports: ["5432:5432"]
mail:
image: axllent/mailpit
ports: ["8025:8025"]
The one rule that matters: the dev database engine and major version must match production. Postgres 14 against a prod Postgres 17 is how GROUP BY quirks and missing extension functions survive locally and explode on deploy.
Onboarding is one command
If a new machine takes more than a short afternoon, the setup has become tribal knowledge. The bar we hold ourselves to: clone, docker compose up -d, copy .env.example to .env, install composer and npm packages, run migrations and seeders. Everything else — PHP extensions, node version (pinned in .nvmrc), postinstall steps — should already be documented or automated. When someone spends half a day on setup, we fix the checklist before anything else, because every future hire pays that cost again.
Seeders are test data, not leftovers
A dev database full of rows called test 2 (do not delete) makes frontend work miserable. We write seeders that produce realistic volume — a hundred orders, not three — with names, dates, and states that look like what production will hold. Empty states, huge lists, and records missing optional fields are where UIs break, and you only find it if the local data pushes on those edges.
Keep env in sync on purpose
Configuration drift between dev and prod causes the sneakiest bugs: a feature flag that's on locally, a queue connection that's sync in dev and Redis in prod, a different filesystem driver. We keep .env.example as the actual template of truth, review changes to it like code, and treat "works in dev only" findings as env bugs first, code bugs second.
Reset beats repair
When a local setup starts behaving oddly, we don't debug it — we rebuild it. docker compose down -v, fresh containers, fresh migrations, reseed. On a good setup this takes minutes. On a snowflake setup — hand-installed extensions, mystery service on port 5432, database built from a months-old dump — a rebuild is a scary proposition, which is itself the signal to fix the setup.
The quiet payoff
A good dev environment disappears from your attention. Tests run against the same database version as production, email lands somewhere you can see, a machine dies and you're back to work in half an hour. The investment is a day or two up front and small ongoing discipline — and it's repaid the first time "works on my machine" stops being the answer to anything.