Stop 17 · CI/CD & DeploymentTopics · 3/9

Environment Configs

Part of the Backend 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 (database URLs, API keys, feature flags) across local, staging and production environments without hardcoding any of it.

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 separate `.env` files (or equivalents) for local, staging and production.
  2. 2Learn to never commit real production secrets to any config file in git.
  3. 3Practice validating required environment variables at app startup, 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