Menu

Topic 6 of 8

Environment Configuration

Learn Environment Configuration for free with explanations, exercises, and a quick test (for Backend Engineer).

Published: January 20, 2026 | Updated: January 20, 2026

Who this is for

Backend Engineers and DevOps-minded developers who deploy services across dev, test, staging, and production and want reliable, secure, repeatable configuration.

Prerequisites

  • Comfort with a terminal and Git
  • Basic understanding of environment variables
  • Optional: Docker basics; basic YAML reading

Why this matters

Real backend work involves deployments that must behave predictably in different environments. You will:

  • Rotate secrets without redeploying code
  • Switch endpoints (e.g., sandbox vs. production) safely
  • Debug issues caused by configuration drift
  • Automate releases where CI/CD injects the right values at the right time

Concept explained simply

Configuration is everything your app needs to run but shouldn’t be hard-coded: URLs, ports, feature flags, timeouts, API keys, database credentials. Treat it as data, not code.

Mental model: knobs and a vault

  • Knobs: non-sensitive settings you can turn per environment (e.g., LOG_LEVEL, FEATURE_X_ENABLED)
  • Vault: sensitive values locked away (DB_PASSWORD, API_KEY). Your app reads them at runtime; they’re never committed to Git.
Key rules you’ll follow
  • 12-Factor principle: store config in the environment
  • Prefer runtime or deploy-time injection over build-time baking
  • Separate config for each environment; avoid dangerous fallbacks
  • Use secrets managers or platform secrets, not plain text in repos
  • Keep env parity: dev ≈ staging ≈ prod (just different values)

Core concepts and decisions

  • Environment types: local, CI, test, staging, production
  • Runtime vs build-time: inject at runtime to avoid rebuilding for each environment
  • Sources of truth:
    • Environment variables (non-sensitive and sensitive)
    • Config files (JSON/YAML) for non-sensitive defaults
    • Secrets stores (Vault, cloud KMS/parameter stores, platform secrets)
  • Precedence example: CLI args > environment variables > config file defaults
  • Configuration as code: version the structure and defaults, never real secrets

Worked examples

Example 1: .env for local + Docker env-file

Goal: run a container that reads values from a .env file without committing secrets.

Files
.gitignore
.env
.env.example
# .env.example (no secrets)
APP_ENV=local
DB_HOST=localhost
DB_USER=appuser
DB_NAME=app
# DB_PASSWORD=<set locally>
# .env (local only; DO NOT COMMIT)
APP_ENV=local
DB_HOST=localhost
DB_USER=appuser
DB_NAME=app
DB_PASSWORD=supersecret
Run
docker run --rm --env-file .env alpine:3.19 sh -c 'echo $APP_ENV $DB_HOST $DB_USER $DB_NAME'

Output should show your values but not expose them in Git.

Example 2: CI runtime injection by branch

Goal: choose environment at deploy time, not build time.

CI pseudo-workflow
name: Deploy
on:
  push:
    branches: [ main, staging ]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Select environment
        run: |
          if [ "${GITHUB_REF_NAME}" = "main" ]; then
            echo "ENV=production" >> $GITHUB_ENV
            echo "API_URL=https://api.example.com" >> $GITHUB_ENV
          else
            echo "ENV=staging" >> $GITHUB_ENV
            echo "API_URL=https://staging-api.example.com" >> $GITHUB_ENV
          fi
      - name: Deploy
        run: |
          echo "Deploying to $ENV with $API_URL"
          # Call your deploy scripts here

Example 3: Kubernetes ConfigMap + Secret

Goal: inject non-sensitive and sensitive settings separately.

Manifests
# configmap.yaml (non-sensitive)
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: info
  FEATURE_X_ENABLED: "true"
---
# secret.yaml (sensitive)
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
stringData:
  DB_PASSWORD: supersecret
---
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 1
  selector:
    matchLabels: { app: web }
  template:
    metadata:
      labels: { app: web }
    spec:
      containers:
      - name: web
        image: your-image:latest
        envFrom:
        - configMapRef:
            name: app-config
        - secretRef:
            name: app-secrets
        env:
        - name: APP_ENV
          value: production

Apply and verify with printenv in the pod. Secrets are mounted as env vars but managed separately.

Step-by-step: set up environment configuration safely

  1. List config: enumerate all settings your service needs. Mark which are sensitive.
  2. Create templates: add .env.example with only placeholders and safe defaults.
  3. Protect secrets: add .env to .gitignore; store real secrets in a secure manager or platform secrets.
  4. Inject at runtime: plan how CI/CD will set variables per environment (branch, tag, or manual promotion).
  5. Fail fast: on startup, validate that required variables exist; abort with clear errors.
  6. Parity checks: ensure the same keys exist across environments; values differ, structure doesn’t.
Startup validation snippet (pseudo-code)
required = ["DB_HOST","DB_USER","DB_PASSWORD","DB_NAME"]
missing = [k for k in required if not env(k)]
if missing:
  exit_with_error("Missing required config: " + ", ".join(missing))

Exercises (do these now)

These mirror the exercises below. Complete them locally and self-check with the checklist.

Exercise 1: Local .env and Docker env-file

Create .env.example and .env. Run a container with --env-file and print APP_ENV, DB_HOST, DB_USER. See the exercise card below for steps.

Exercise 2: Branch-based CI config selection

Write a simple CI workflow that echoes which environment is selected for staging vs main. See the exercise card below.

Self-check checklist

  • Your repository contains .env.example but not .env
  • Running the container prints expected values from .env
  • CI workflow prints “Deploying to staging” on staging branch and “Deploying to production” on main branch
  • There are no dangerous default fallbacks for secrets

Common mistakes and how to self-check

  • Committing secrets: even in private repos. Self-check: search for DB_PASSWORD, API_KEY in Git history; add pre-commit checks.
  • Baking config at build-time: forces rebuild for each environment. Self-check: does the same image work in staging and production? If not, move config to runtime.
  • Missing validation: app starts with empty vars. Self-check: add startup validation and fail fast.
  • Drift between environments: keys exist in prod but not staging. Self-check: compare key sets automatically in CI.
  • Overusing secrets for non-sensitive values: increases friction. Self-check: classify values; keep only sensitive in secret stores.
Quick parity script idea
# Compare key sets between two files
comm -3 <(sed 's/=.*//' .env.staging | sort) <(sed 's/=.*//' .env.production | sort)

Practical projects

  • Convert an existing service to runtime config: add .env.example, startup validation, and CI injection.
  • Dockerize a small API and run it against staging and production just by changing env files; same image, different values.
  • Kubernetes version: split non-sensitive ConfigMap and Secret, and verify printenv shows correct values.

Learning path

  • Now: Environment configuration foundations
  • Next: Secrets management and rotation
  • Then: CI/CD deployment strategies (staged rollouts, blue/green)
  • Later: Observability of config changes (auditing, change logs)

Next steps

  • Finish Exercises 1–2 below
  • Run the Quick Test to validate understanding
  • Apply these patterns to one of your services this week

Mini challenge

You need dev, staging, and prod for an API. Design where each of these lives and how values are injected:

  • LOG_LEVEL (non-sensitive)
  • PAYMENTS_API_KEY (sensitive)
  • FEATURE_CHECKOUT_V2 (boolean)
  • DB_CONNECTION_STRING (sensitive)
Think it through
  • Non-sensitive: ConfigMap or env var; version defaults
  • Sensitive: secret store or platform secrets; injected at runtime
  • Feature flag: env var or feature flag service; enable per environment
  • Fail fast on missing sensitive values

Ready to test yourself?

Quick test is available to everyone; only logged-in users get saved progress.

Practice Exercises

2 exercises to complete

Instructions

Goal: keep secrets out of Git while running locally with correct values.

  1. Create a .gitignore entry: .env.
  2. Create .env.example (no secrets):
    APP_ENV=local
    DB_HOST=localhost
    DB_USER=appuser
    DB_NAME=app
    # DB_PASSWORD=<set locally>
    
  3. Create .env (local only):
    APP_ENV=local
    DB_HOST=localhost
    DB_USER=appuser
    DB_NAME=app
    DB_PASSWORD=supersecret
    
  4. Run a one-off container that prints values:
    docker run --rm --env-file .env alpine:3.19 sh -c 'echo APP_ENV=$APP_ENV DB_HOST=$DB_HOST DB_USER=$DB_USER DB_NAME=$DB_NAME'
    
  5. Confirm the output matches your .env values and that .env is not tracked by Git.
Expected Output
APP_ENV=local DB_HOST=localhost DB_USER=appuser DB_NAME=app

Environment Configuration — Quick Test

Test your knowledge with 8 questions. Pass with 70% or higher.

8 questions70% to pass

Have questions about Environment Configuration?

AI Assistant

Ask questions about this tool