Stop 15 · DevOps & DeploymentTopics · 6/8

Environment Configs Across Environments

Part of the Full Stack Developer Roadmap

In short

Your app runs in at least three places: your laptop, staging and production. Same code, different database, keys and log levels. Mixing them up is how test emails reach real customers.

Managing different configuration for local, staging and production across both your frontend and backend — so a config mistake doesn't mean your staging frontend talks to your production database.

The purple blob cooks the same curry in three pots of different sizes, picking a different spice box for each.

Think of it like this

One recipe, three kitchens

A caterer uses the same biryani recipe at home, at a tasting and at a 500-guest wedding. Only the pot size and spice quantities change. Your code is the recipe. Config is the quantities, kept separately for each kitchen.

Watch it happen

Where a config value comes from

Defaults
.env file
Real env
Validate
App starts

1/5Code ships with safe defaults, like PORT=3000 and LOG_LEVEL=info.

The big ideas

What you need to know

Same build, different config

Change behaviour by changing values, not code. The artifact you tested on staging is the one that runs in production.

Secrets never go in Git

Commit a .env.example with dummy values. The real .env sits in .gitignore.

Validate at startup

Check every variable when the app boots and fail loudly. A missing value should stop the deploy, not a user's checkout.

NODE_ENV is not enough

NODE_ENV=production tweaks library behaviour. Use your own APP_ENV, like staging or production, for your own logic.

See it in code

Walk through the code

ts
1// config.ts2import { z } from "zod";3 4const schema = z.object({5  APP_ENV: z.enum(["development", "staging", "production"]),6  PORT: z.coerce.number().default(3000),7  DATABASE_URL: z.string().url(),8  LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),9});10 11const parsed = schema.safeParse(process.env);12if (!parsed.success) {13  console.error("Bad config:", parsed.error.issues);14  process.exit(1);15}16export const config = parsed.data;

1/4Describe every variable once: allowed values, type and default.

Every other file imports config from here. If a variable is missing or wrong, the app refuses to start and tells you exactly which one.

Save yourself the pain

Common mistakes

Oops

Committing .env 'just for now' to share it with a teammate.

Do this instead

Once it's in Git history it's leaked, even after deleting. Rotate the keys and share secrets through a password manager.

Oops

Writing if (process.env.NODE_ENV === 'production') all over the code.

Do this instead

Read one config object with clear flags like config.sendRealEmails. Easier to test and to change.

Your mission

Lock down your config

~20 min

Quick check

Staging and production run the same Docker image. How should each end up using a different database?

Go deeper

  1. 1Set up distinct environment variable sets for local, staging and production on both apps.
  2. 2Practice promoting a change from staging to production deliberately, not accidentally.
  3. 3Validate required environment variables at startup on the backend, failing fast if any are missing.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge