If You Can't Run It Local, You're Not Ready to Deploy
Deploy Theater
Plenty of teams ship every day. CI is green. There is a runbook somewhere. Then a bug shows up under a specific payload, migration, or timing window - and nobody can reproduce it without staging access, a VPN, three shared accounts, and a Slack thread titled "can someone restart the mock for me?"
That is not shipping discipline. That is deploy theater: the appearance of readiness without the ability to own a failure on your own machine.
If a new engineer - or you, on a fresh laptop - cannot bring the critical path up with a short, documented path, you do not have a deploy story. You have a hope story.
What "run it local" actually means
It does not mean cloning production into Docker Desktop. You do not need every managed service, every regional failover, every edge case of the cloud. It means enough fidelity for the work developers actually do:
- A real data store (or a close stand-in) with durable volumes
- Mocks for dependencies you do not own
- An observability path you can hit from your app process (OTLP, logs, traces)
- Config, APIs, and protocol tools you can exercise without a cloud account
Local is not a toy. Local is the cheapest place to be wrong.
What dev-tools does for local run
I keep that fidelity in shivakadiri/dev-tools: a Docker Compose stack of development services. One command:
docker compose up -d
It is not an application. It is the local dependency plane - so your app (or agent, workflow, or API) can run against real-ish infrastructure on localhost instead of a shared cluster.
Bootstrap
- Clone the repo, copy
.env.exampleโ.env docker compose up -d(ordocker compose up -d <service>for a subset)- Open documented URLs - Postgres on
5432, Grafana on3000, WireMock on9000, and the rest on fixed ports from.env
A Homepage dashboard aggregates those links so you are not hunting ports in a notes file.
What you can actually do locally
- Persist and query data - shared PostgreSQL with pgvector on
localhost:5432. Several services (n8n, LiteLLM, Hoppscotch) use the same instance with separate databases. pgAdmin is wired for admin work without installing a client. - Stub external APIs - WireMock Studio on
9000(plus a port range for stubs) so you can simulate third-party failure modes, latency, and contract drift without calling production. - Emit and inspect telemetry - Grafana (otel-lgtm) with Loki, Tempo, Prometheus, and an OTel collector. Point your app at OTLP gRPC
4317or HTTP4318and debug traces/logs/metrics before anything hits a cloud backend. - Automate and integrate - n8n for workflows against local services and mocks.
- Call APIs end-to-end - Hoppscotch as a local API client against WireMock, your app, or anything else on the compose network / host.
- Run config without Azure - Azure App Configuration emulator with anonymous UI and HMAC keys, so feature flags and config clients work offline.
- Run LLM work offline - Ollama for inference, Open WebUI for chat, LiteLLM as an OpenAI-compatible proxy (
/chat/completions) in front of local or cloud models. - Debug agents and MCP - A2A Inspector and MCP Inspector. When the agent or MCP server runs on the host, containers reach it via
host.docker.internal(documented in the README).
Design choices that matter for DX
- One shared Postgres instead of a database container per tool - fewer volumes, simpler backups, one place to reset state.
- Ports and secrets in
.env- predictable localhost endpoints, not random published ports. - Honest failure modes in docs - e.g. Ollama OOM-killed on Mac when Docker Desktop memory is tight; App Config emulator via
platform: linux/amd64on Apple Silicon. That honesty is DX too. A local path that only works on a perfect day is not a local path.
I am not arguing that every team should run this exact toolbox. I am arguing that having a dependency plane you can start in minutes is how you treat developer experience as infrastructure. Steal the pattern: github.com/shivakadiri/dev-tools.
Why local fidelity makes deploy honest
When that plane runs on your laptop:
- Feedback loops shrink from "wait for staging" to "change it and look" - migrations, stubs, OTLP, config.
- Experiments stay isolated. Blow up a volume. Reset WireMock. Replay the bad payload. Nobody else is blocked.
- You exercise the same observability path you will need in production - traces and logs on the happy path, not only during an incident.
- Onboarding is a checklist, not tribal knowledge: clone โ env โ compose up โ hit a known URL.
Deploy then becomes a promotion of something you already understand. Not a leap of faith into an environment only three people can access.
DX debt looks familiar
You will recognize the anti-patterns:
- Docs that only describe cloud provisioning
- Environments that exist only behind secrets and shared clusters
- "Just use staging" as the default development workflow
- Local setup that takes a day and a tribal knowledge session
Those are not "we'll improve DX later" items. They are release risk. Every hour a developer cannot reproduce a bug locally is an hour the incident clock runs without a controlled repro.
The bar I use
Before I trust a deploy story, I want this path to be boring:
- Clone the repo
- Copy
.env.example(or equivalent) docker compose up -d(or the project's equivalent one-liner)- Open a documented URL / hit a documented port
- See something real working - a UI, an API, a dashboard, a successful OTLP export - in minutes, not days
If step 5 requires a ticket, a shared password in a vault nobody can find, or a cluster that is "usually up," the bar failed.
Own the failure path
I'm big on developer experience because shipping is a human system. As a leader, self-sufficiency is the goal: every engineer should be able to reproduce the weird path, poke the edge case, and own the fix. A local dependency plane - tools, docs, and compose up - is how you make that real.
If you cannot run it local, you cannot fully debug it, reproduce it, or teach it. Deploying anyway is guessing. And guessing is the opposite of good developer experience.
Comments
No comments yet. Start the discussion.