Local, staging and production

Why teams run the same app in more than one place, and what lives in each.

20 min read

You run pnpm dev and open localhost:3000. Your teammate opens a preview link on a pull request.

A shopper opens shop.com. All three see the same app, but each one runs in a different place.

Each place is an . Most teams have three: local, staging and production. In this lesson you learn what lives in each, and why the team keeps them apart.

Three places, one app

Each environment has a job:

  • Local is for building.
  • Staging is for checking.
  • Production is for real users.
A change moves left to right. Each environment has its own data.
  • Local is your laptop. You can break it a hundred times a day, and nobody else notices.
  • Staging is a shared copy that looks like production. The team and the robots test there before users see a change. Some teams call it "pre-production", "QA" or "test".
  • Production is the live app. Real users, real money, real data.

What lives in each

The code is the same in every environment. The things around the code are not. Here is a typical setup for a Next.js shop:

LocalStagingProduction
Who uses itYouYour teamReal users
Addresslocalhost:3000staging.shop.comshop.com
DatabaseFake seed dataTest dataReal customer data
Stripe keysTest keysTest keysLive keys
EmailsCaught, not sentSent to the teamSent to customers
When it breaksOnly you noticeThe team noticesUsers notice

Look at the Stripe row. Stripe gives every account two sets of keys:

  • A test key accepts fake card numbers and moves no money.
  • A live key charges real cards.

A test key in production means no orders get paid. A live key on your laptop means a test charges a real card. The environment decides which key the app gets.

One build, different settings

Last lesson had a rule: build once, and send the same artifact everywhere. So how does one artifact know which database to use?

The answer is .

The app does not contain the database address. It reads the address when it starts. Each environment gives it a different value.

One artifact, three sets of settings. Only the settings change.

The most common way to pass settings is an . In a Next.js app you already use them:

.env.local
# Read by next dev on your laptop. Never committed.
DATABASE_URL=postgres://localhost:5432/shop
STRIPE_SECRET_KEY=sk_test_51Hx...
  • DATABASE_URL tells the app where its database is. On your laptop, that is a database on your own machine.
  • sk_test_ at the start of the key marks a Stripe test key. A live key starts with sk_live_.
  • On Vercel, you do not use this file. You set the same names in the project settings, with one value for Preview and one for Production.

Docker in Action shows the same idea with containers. A greetings service ships as one image. At deploy time, a variable called DEPLOY_ENV picks a settings file for dev, stage or prod. The image never changes.

The real pipeline on Vercel

Here is how a Next.js team on Vercel moves one change through its environments:

GitHub
Pull requesta branch on GitHub
two paths
every push

preview

every push

  1. Vercel
    Previewits own URL
  2. Preview vars
    PostgreSQL
    Test databasefake orders
approved

production

approved

  1. Merge to mainafter review
  2. Vercel
    Productionshop.com
  3. Production vars
    PostgreSQL
    Real databasereal orders
The same code reaches two environments. The variables decide which database each one uses.

Vercel gives every project three environments: Local, Preview and Production. Paid plans can add named ones, such as staging. Each environment has its own set of variables.

Keep them alike, keep them apart

Two rules pull in different directions, and good teams follow both.

Keep them alike. Staging only helps if it acts like production. If production runs PostgreSQL 16 and your laptop runs SQLite, a query can pass locally and fail live. The Twelve-Factor App guide calls this the gap between dev and prod. It asks teams to use the same kind and version of each service in every environment. Docker, later in this course, makes that much easier.

Keep them apart. Staging must never touch production data by mistake. Separate databases, separate keys and separate access stop a test from hurting real users.

Many teams now make production look different on purpose: a red banner in the admin page, a red shell prompt on production servers, and fewer people with access. The goal is simple. You should always know where you are.

Remember4 points
  1. 01

    Most teams run the same app in three environments: local for building, staging for checking, production for real users.

  2. 02

    The code is the same everywhere. Configuration, often environment variables, tells each copy which database and keys to use.

  3. 03

    On Vercel, every pull request gets a preview deployment, and Preview and Production each get their own variables.

  4. 04

    Keep environments alike so tests mean something, and apart so a test never touches real data.

Read more

Next, you get a map of the DevOps toolbox: every tool you will hear about, sorted by its job.