✦ For everyone, free.

Practical knowledge for real and everyday life

Home

14.2.1.2 Staging Environment Config

A focused guide to Staging Environment Config, connecting core concepts with practical Docker and container operations.

Staging environment configuration for Docker defines how a pre-production environment is set up to mirror production closely enough that issues surface before deployment, while still allowing the controlled differences, such as scaled-down resources or synthetic data, that make staging practical to run and safe to experiment against.

The purpose staging serves

Staging exists to catch the class of problem that unit tests and a developer's local environment cannot: integration issues between services, configuration that behaves differently under realistic load, and deployment-process mistakes. For staging to fulfill that role, its configuration must track production closely rather than being treated as an extension of the development setup.

docker pull registry.example.com/my-api:1.4.0
docker run -d --env-file staging.env registry.example.com/my-api:1.4.0

The image pulled and run in staging should be the identical artifact, by digest if possible, that will later be promoted to production, not a separately built variant.

docker pull registry.example.com/my-api@sha256:3f29a8c1...

What should match production exactly

Several aspects of the configuration should be identical between staging and production to make staging a meaningful predictor of production behavior:

  • The container image itself, including its base image and installed dependency versions.
  • The Docker Compose service topology or Swarm/cluster service definitions, including health checks and restart policies.
  • The network architecture between services, including which ports are internal-only versus externally published.
  • The daemon-level settings that affect container behavior, such as logging drivers and resource limit enforcement.
services:
  api:
    image: registry.example.com/my-api:1.4.0
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/healthz"]
      interval: 30s
      retries: 3
    deploy:
      restart_policy:
        condition: on-failure

Keeping this definition shared between staging and production, with only environment-specific values overridden, ensures that a health check or restart policy that fails to catch a real problem will fail to catch it identically in both places, rather than masking an issue that only staging would have revealed.

What is acceptable to differ

Some differences between staging and production are reasonable and do not undermine staging's usefulness:

services:
  api:
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 256M
services:
  api:
    deploy:
      replicas: 5
      resources:
        limits:
          memory: 1024M

Lower replica counts and tighter resource limits in staging are acceptable because they are scale differences, not behavioral differences; a correctly written health check or restart policy should still function correctly with one replica, just less redundantly.

Synthetic and anonymized data

Staging databases are typically seeded with synthetic or anonymized data rather than a raw production copy, both for privacy reasons and because the seeding process itself becomes a useful, repeatable verification of the deployment pipeline:

docker exec staging-db psql -U postgres -d app -f /seed/staging-fixtures.sql

If a production data copy is genuinely needed to reproduce a hard-to-trigger bug, it should go through an anonymization step before landing in staging, and access to that anonymized copy should still be governed by data handling policy.

External dependencies and staging equivalents

Production depends on real external services: payment processors, email providers, third-party APIs. Staging configuration typically points these at sandbox or test-mode equivalents rather than disabling the integration entirely, which preserves the ability to test the integration path without it producing real-world side effects:

docker run -e PAYMENT_API_URL=https://sandbox.payments.example.com \
           -e PAYMENT_API_KEY=sk_test_xxx \
           my-api

Mocking out external dependencies entirely in staging defeats much of the purpose of having a pre-production environment, since the integration boundary is exactly where production incidents often originate.

Promotion rather than rebuild

A staging configuration strategy should make promotion to production a configuration change, not a rebuild:

docker compose -f docker-compose.yml -f docker-compose.staging.yml up -d
docker compose -f docker-compose.yml -f docker-compose.production.yml up -d

The base file defines the shared shape of the deployment; the environment-specific override file is the only thing that changes between the two commands, which keeps the promotion step simple, fast, and low-risk.

Verifying staging actually reflects production

A staging environment configuration is only valuable if periodically validated against the production configuration it is meant to mirror:

docker exec staging-api env | sort > staging.env
docker exec production-api env | grep -v PASSWORD | sort > production.env
diff staging.env production.env

A diff that surfaces unexpected divergence, beyond the intentional scale and data differences, is a signal that staging configuration maintenance has lagged behind production and needs reconciliation before it is trusted again as a predictor of production behavior.

Common mistakes

  • Letting staging run an older or differently built image than what will actually be deployed to production, undermining the entire premise of using staging as a predictor.
  • Disabling or mocking external integrations entirely in staging instead of using sandbox equivalents, which removes the chance to catch integration-boundary issues before they reach production.
  • Allowing staging's Compose or deployment definition to drift independently from production's over time, until the two configurations no longer share enough structure to be comparable.
  • Seeding staging with a raw, unanonymized copy of production data, creating a privacy and compliance exposure with no corresponding benefit over a synthetic or anonymized fixture set.
  • Treating staging configuration as a one-time setup rather than something that needs the same ongoing maintenance and parity checks as production itself.

A staging environment configuration earns its value by tracking production's image, topology, and health and restart behavior closely, differing deliberately only in scale and data, and being verified periodically rather than assumed to still match what production is actually running.