Back to Blog
Guide11 min read2026-07-09

How to Set Up Dev, Staging, and Production Environments

Shipping straight to prod is how outages happen. This guide covers designing dev, staging, and production environments, environment parity, config separation, and a clean promotion workflow that catches bugs before users do.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Why separate environments

The purpose of multiple environments is to catch problems progressively, the further a bug gets, the more it costs. A clean setup gives you:

  • Dev: fast iteration, breakable, per-developer or per-branch.
  • Staging: a production-like rehearsal space where you validate before release.
  • Production: the real thing, changes only via a controlled promotion.

The goal isn't bureaucracy; it's confidence. You want to be *boringly certain* a change works before it reaches users.

The cardinal rule: config in the environment, not the code

The twelve-factor principle that makes everything else possible: the same build artifact runs in every environment, and only configuration changes. You should never have if (env === 'production') branches that alter behavior, and you should never rebuild differently per environment. Same image, different env vars.

# Same code, different config per environment
# dev
DATABASE_URL=postgres://.../app_dev
LOG_LEVEL=debug
STRIPE_SECRET_KEY=sk_test_...

# production
DATABASE_URL=postgres://.../app_prod
LOG_LEVEL=info
STRIPE_SECRET_KEY=sk_live_...

If the artifact differs between staging and prod, then staging didn't actually test what you're shipping.

Environment parity matters

Staging is only useful to the degree it resembles production. The closer the parity, the more bugs it catches. Aim to match:

  • Same database engine and version (don't test on SQLite and ship on Postgres).
  • Same runtime versions and dependencies (the identical image).
  • Similar topology (if prod has a cache and a queue, staging should too).

Where parity is expensive (you rarely need prod-scale data volumes in staging), document the difference so you know what staging *won't* catch.

The branch-per-environment pattern

A clean, common workflow maps Git branches to environments:

BranchEnvironmentTrigger
feature/*ephemeral / devpush (preview)
develop or mainstagingmerge
production (or a tag)productionmerge/tag, gated

A change flows: feature branch → merged to staging branch (auto-deploys to staging) → validated → merged/promoted to production branch (auto-deploys to prod). Promotion is just a merge, which means it's reviewable and revertable.

Each environment needs its own data

Never point staging at the production database. Beyond the obvious risk of corrupting real data, it's a privacy and compliance problem. Each environment gets its own database, with staging seeded from a sanitized/anonymized snapshot of prod when you need realistic data.

Setting this up on PandaStack

The cleanest pattern on PandaStack is one project per environment, each connected to its own branch, each with its own managed database:

  1. 1Staging project connected to your staging/main branch, with its own managed Postgres. PandaStack injects that environment's DATABASE_URL automatically, so the same code connects to the staging DB without any code change.
  2. 2Production project connected to your production branch (or tag), with its own managed Postgres and its own injected DATABASE_URL.
  3. 3Set environment-specific values (log level, API keys, STRIPE_SECRET_KEY test vs live) as env vars on each project.

Because PandaStack auto-wires the database per environment, you avoid the classic mistake of staging accidentally talking to the prod database, each project has its own injected connection string.

# Staging project env
LOG_LEVEL=debug
STRIPE_SECRET_KEY=sk_test_...
# DATABASE_URL injected -> staging DB

# Production project env
LOG_LEVEL=info
STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL injected -> production DB

PandaStack's free tier (5 web services, 1 database) is generous enough to run a real staging environment cheaply for smaller apps; as you grow, paid plans give you more databases and connections per environment.

The promotion workflow in practice

  1. 1Developer opens a PR from a feature branch. CI runs tests.
  2. 2On merge to the staging branch, PandaStack rebuilds and deploys to the staging project automatically (git-push deploys).
  3. 3QA / smoke tests run against staging. Check the live logs and metrics.
  4. 4When staging is green, merge the staging branch into the production branch.
  5. 5PandaStack builds and deploys to production. If anything's wrong, use deploy history to roll back instantly.

The key safety property: production only ever receives code that already ran successfully in staging from the same artifact.

Database migrations across environments

Migrations are where environment promotion most often goes wrong. Rules:

  • Run migrations as part of each environment's deploy, in order.
  • Make migrations backward-compatible so a rollback of code doesn't break against a migrated DB (expand/contract pattern: add columns before removing the old path).
  • Test migrations on staging (with a prod-like snapshot) before they touch production.

Honest trade-offs

More environments mean more infrastructure to pay for and maintain, and there's a real temptation to let staging rot until it no longer matches prod, at which point it gives false confidence. Keep staging honest: same artifact, same engine versions, regularly refreshed. If you're a solo developer on a tiny project, a single staging environment plus solid local dev may be enough; don't build a five-environment pipeline for a side project.

Conclusion

Good environment management is mostly discipline: one build artifact, config in the environment, real parity in staging, separate databases per environment, and promotion via reviewable merges. Get those right and most bugs die in staging instead of paging you at 2am.

PandaStack makes per-environment projects cheap by auto-wiring a separate managed database into each one, so staging and prod stay cleanly isolated. Set up your first staging project on the free tier at https://dashboard.pandastack.io.

References

  • The Twelve-Factor App, config: https://12factor.net/config
  • The Twelve-Factor App, dev/prod parity: https://12factor.net/dev-prod-parity
  • Expand/contract migration pattern: https://martinfowler.com/bliki/ParallelChange.html
  • GitHub flow: https://docs.github.com/en/get-started/using-github/github-flow

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Guide

Browse all Guide articles →

See also