Updated Nov 5, 20266 min read

Staging vs Production: How to Set Up Deployment Environments for a Backend

What development, staging, preview and production environments are for, what has to match between them and what should differ, and how to keep them in sync without copying infrastructure config by hand.

Most backend outages that come from a deploy trace back to a difference between the place a change was tested and the place it ran: a migration that worked on a small dataset, an environment variable that existed in one place and not the other, a queue that was in-memory in testing and real in production. Deployment environments exist to catch those differences early, and they only do that if they stay alike in the ways that matter.

This guide covers what each environment is for, what has to match between staging and production, what should deliberately differ, and how to keep them in sync without maintaining several copies of infrastructure config.

The usual deployment environments

Local is the developer's machine, and it should run the same kinds of infrastructure as the cloud, such as Postgres instead of SQLite, so a test passing locally means something.

Preview environments are created per pull request and torn down after merge. They let reviewers and coding agents check a change against a running backend before it lands. The preview environments guide covers them in depth.

Staging, sometimes called development or pre-production, runs the main branch continuously. It is where changes from several pull requests meet, where integration with third-party services gets exercised, and where migrations run against data before they run in production.

Production serves real users, and everything about it, including access, backups, scale and monitoring, is set for reliability rather than speed of change.

What has to match between staging and production

Staging is only useful as a rehearsal if the parts that cause failures are the same:

  • Infrastructure types. The same database engine and major version, the same queue service, the same object storage. A staging that uses a different message broker tests a different system.
  • The deploy process. Staging should be deployed by the same pipeline, with the same build, the same migration step and the same rollout. A release that is deployed differently in production is untested in the place it matters.
  • Configuration shape. Every config key and secret production reads should exist in staging, even if the values differ. Missing keys are one of the most common causes of a broken first production deploy.
  • Migrations. Run them in staging first, against data large enough to show slow or locking migrations.

What should differ

  • Scale. Smaller instances and a single database instance keep staging cheap. Production can be Multi-AZ with read replicas.
  • Data. Production data stays in production, and staging gets seed data or anonymized snapshots.
  • Secrets. Separate API keys for payment providers, email services and model providers, ideally in test or sandbox modes, so a staging bug cannot charge a card or email a customer.
  • Access. More developers can reach staging than production, and production changes usually need review or approval.

The drift problem

Environments drift over time: a database parameter changed by hand in production, a queue added to staging's Terraform but not yet to production's, or a secret rotated in one place and forgotten in the other each make staging less representative. Teams typically fight drift with infrastructure as code per environment, shared modules and plan reviews, and every new resource then has to be added in several places and kept in sync.

Environments with Encore

Encore

Encore is infrastructure from code for TypeScript and Go, with a platform that provisions and deploys each environment into your own AWS or GCP account. The infrastructure an application needs is declared in its code, so a database or topic added in a pull request exists in every environment once that code is deployed there, and there is no per-environment copy to forget.

The Infrastructure page in the Encore dashboard with a database's configuration panel open, showing fields for vCPUs, memory, storage, availability type, max connections and backups.
Each provisioned resource can be reviewed and its configuration edited per environment in the Encore dashboard. The screenshot shows a GCP Cloud SQL database.

A billing service that uses a database and a payment provider key:

// billing/billing.ts import { api } from "encore.dev/api"; import { secret } from "encore.dev/config"; import { SQLDatabase } from "encore.dev/storage/sqldb"; const db = new SQLDatabase("billing", { migrations: "./migrations" }); const paymentKey = secret("PaymentProviderKey"); export const charge = api( { expose: true, method: "POST", path: "/charges" }, async ({ customerId, cents }: { customerId: string; cents: number }): Promise<{ id: number }> => { // Call the payment provider with paymentKey() here. const row = await db.queryRow<{ id: number }>` INSERT INTO charges (customer_id, cents) VALUES (${customerId}, ${cents}) RETURNING id `; return { id: row!.id }; }, );

The migration in billing/migrations/1_create_charges.up.sql creates the table:

CREATE TABLE charges ( id SERIAL PRIMARY KEY, customer_id TEXT NOT NULL, cents INT NOT NULL );
Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

~
$ encore app create billing --example=ts/empty$ encore secret set --type dev,preview,local PaymentProviderKey$ encore secret set --type prod PaymentProviderKey

The test-mode key goes to development, preview and local environments and the live key only to production, which gives you the separate secrets described above without a second config file. A deploy to a cloud environment fails when a secret the code reads has no value there, which catches the missing-key problem before users do. Secret values are not stored in the repository, so an agent reading the repository cannot read or leak them. See the secrets docs for the details.

Every Encore app starts with an environment named staging, a development environment that deploys each push to the main branch. You add production in the dashboard as a Production environment, choose AWS or GCP, set its deploy trigger to a branch or manual, and can turn on infrastructure approval so an admin approves infrastructure changes before a deploy proceeds, as described in the environments docs. Each environment can also use single or separate processes for services without code changes, which lets staging run everything in one process while production scales services independently.

On AWS, a production database is RDS with Multi-AZ and backups, and Encore provisions an IAM role for the service. Migrations run in sequence on each deploy before the new code starts, as covered in changing the database schema. With the GitHub integration connected, staging and production both deploy from Git pushes, so the same code goes through the same pipeline on its way to users.

  1. Local development that runs the same infrastructure types as the cloud.
  2. A preview environment per pull request.
  3. Staging deploying main automatically, with test-mode secrets and seed data.
  4. Production deploying on a release branch or manually, with approval for infrastructure changes.

Keep the deploy process identical across the last three and the differences limited to scale, data, secrets and access, and staging will catch most of what would otherwise reach users.

Frequently asked questions

What is the difference between staging and production?

Production serves real users and real data, while staging is a copy of production's setup, with the same kinds of infrastructure and the same deploy process, used to test a release before it reaches users, usually with smaller instances and test data.

Do I need a staging environment?

If you have paying users, yes, since staging is the last place to catch migrations, config mistakes and integration failures before users see them. Teams that ship small changes behind preview environments per pull request sometimes keep staging minimal, but they still need somewhere that runs the production setup end to end.

Should staging use production data?

Usually not, since copying production data into staging spreads personal data to an environment with weaker access controls. Seed scripts, anonymized snapshots or synthetic data cover most testing needs.

How does Encore handle staging and production?

Every Encore app starts with a development environment called staging that deploys each push to main, and you add a production environment in your own AWS or GCP account. Both run the same code, and Encore provisions the infrastructure each one needs, while secrets are set per environment type.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders