Environments: local, staging, production

ProcessTier 1 · hear it this weekPhase 6 · Run it like a company

Separate copies of the product for different purposes: a developer's laptop, a staging rehearsal, and production where real users are.

Testing risky changes on the live product would break it for real users, but you still need somewhere realistic to try them. Environments exist as separate copies, local, staging, and production, so work can be built and verified safely before it reaches customers.

Code moves through stages. Local is the engineer's own machine. Staging is a shared copy that mirrors production, where changes are tested safely with fake or copied data. Production is the real thing, with real users and real money.

Keeping them separate means changes are tried and proven before they ever touch users. "Test it in staging first" is this discipline.

A junior PM, stuck

Customers kept failing at payment during Friday's lunch rush, so I filed a bug. The dev closed it an hour later with one line, "works fine on staging", and production customers are still failing. I know staging and production are different somehow, but I cannot say how, so I have no grounds to reopen the ticket.

That one line is not a lie, it is just not an answer, and the reason lives in two small files. Below are the actual .env files the staging and production copies of TiffinBox read at startup, side by side, with only the live payment key masked. Everything that separates "works on staging" from "works for customers" is in the lines that do not match. Five minutes of reading and you can reopen that ticket with a reason.

The .env files staging and production read at startup, live key masked
staging.env
APP_ENV=staging
DATABASE_URL=postgres://db-staging.internal/tiffinbox_copy
SESSION_COOKIE=tb_session
MENU_CACHE_TTL_SECONDS=300
PAYMENT_GATEWAY_MODE=sandbox
PAYMENT_GATEWAY_KEY=pk_test_tb_4f2a91
NEW_CHECKOUT=on
production.env
APP_ENV=production
DATABASE_URL=postgres://db-prod.internal/tiffinbox
SESSION_COOKIE=tb_session
MENU_CACHE_TTL_SECONDS=300
PAYMENT_GATEWAY_MODE=live
PAYMENT_GATEWAY_KEY=pk_live_****************
NEW_CHECKOUT=off

Click a step to see the lines it points at.

Accepting "works fine on staging" as closure on a production bug. It proves the staging combination of settings works; your ticket is about production's combination, so it stays open until someone reproduces with production's flag and gateway.
Demoing a feature on staging and telling stakeholders it is live. A flag can be on in staging and off in production, so the room just saw something customers cannot see yet.
Filing a payment bug without naming the environment. A sandbox failure moves no money and a live one does, so devs triage them differently; name the environment in the first line of the ticket.
Asking for the production .env to be pasted into a ticket so you can compare. The live key is a secret; ask "which settings differ between staging and prod for checkout" and let the dev answer in words.

Reopen the ticket with: "Staging runs the new checkout flag on, against the sandbox gateway and a copied database. Production runs the flag off against the live gateway, which is where customers are failing. Can you retest with production's combination?" You read two config files, found the lines that differ, and turned a closed ticket into a precise retest.

"Can you demo it?" usually means on staging; "is it live?" means in production. The distinction avoids confusion.
Staging that doesn't match production causes "worked in staging, broke live" bugs; asking "how close is staging to prod?" is worthwhile.

"It's on staging now, ship to production after we verify."

Appears in Phase 6, Run it like a company.