✦ For everyone, free.

Practical knowledge for real and everyday life

Home

14.2 Production Config

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

Production configuration for Docker covers how runtime settings, environment-specific values, and daemon-level behavior are supplied to containers and to the Docker Engine itself in a way that is reproducible, auditable, and distinct from the values used in development, without baking environment-specific decisions into the image.

Keeping configuration out of the image

An image built for production should be the same artifact that was tested in staging; anything that differs between environments belongs outside the image, supplied at run time. Baking environment-specific values into the image (a production database hostname, for instance) forces a rebuild for every environment and breaks the guarantee that the exact same artifact moved through the pipeline.

FROM node:20
WORKDIR /app
COPY . .
RUN npm ci --production
CMD ["node", "server.js"]

Note the absence of any ENV line setting a database URL or API key; those are supplied externally, not fixed at build time.

Environment variables

The most common mechanism for supplying production configuration is environment variables, passed at container start:

docker run -e NODE_ENV=production -e DATABASE_URL=postgres://db:5432/app my-api

For more than a handful of values, an env file keeps the invocation manageable and the values out of shell history:

docker run --env-file production.env my-api
NODE_ENV=production
DATABASE_URL=postgres://db:5432/app
LOG_LEVEL=warn

In Compose, the same file is referenced declaratively:

services:
  api:
    image: my-api:latest
    env_file:
      - production.env

Separating secrets from ordinary configuration

Values like database hostnames or log levels are not sensitive, but credentials and tokens are, and conflating the two in a single env file makes it harder to apply the access controls and rotation policies that secrets require. Docker Compose and Swarm support a dedicated secrets mechanism that mounts a value as a file inside the container rather than as an environment variable, avoiding exposure through docker inspect or process environment dumps:

services:
  api:
    image: my-api:latest
    secrets:
      - db_password

secrets:
  db_password:
    external: true
cat /run/secrets/db_password

The application reads the secret from the mounted file path rather than from an environment variable, which keeps it out of docker inspect output and out of any environment variable leakage between processes.

Configuration files via bind mount or config object

Structured configuration, such as a full application config file, is often better supplied as a file than flattened into many individual environment variables:

docker run -v /etc/myapp/production.yaml:/app/config.yaml:ro my-api

Swarm's config objects serve a similar purpose for non-secret structured configuration in a clustered deployment:

services:
  api:
    image: my-api:latest
    configs:
      - source: app_config
        target: /app/config.yaml

configs:
  app_config:
    external: true

Daemon-level production configuration

Beyond the application's own configuration, the Docker daemon itself has settings that matter in production, set through /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "live-restore": true,
  "default-address-pools": [
    { "base": "172.30.0.0/16", "size": 24 }
  ]
}
systemctl restart docker

live-restore is particularly relevant to production: it allows running containers to keep running uninterrupted if the daemon itself is restarted, rather than stopping every container whenever the daemon process restarts.

Resource limits as configuration

Production configuration also includes the resource boundaries a container runs within, since an unconstrained container can starve other workloads on the same host:

docker run --memory=512m --cpus=1.5 my-api
services:
  api:
    image: my-api:latest
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "1.5"

Overriding configuration per environment with Compose

Compose supports layering an override file on top of a base file, which keeps a single source of truth for the service definition while still allowing environment-specific values to differ:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
services:
  api:
    environment:
      - NODE_ENV=production
    deploy:
      replicas: 3

Validating configuration before deployment

Because production configuration is supplied externally, a missing or malformed value is a runtime failure rather than a build-time failure, which makes startup-time validation important. An application should fail fast and loudly if a required configuration value is absent, rather than starting in a partially configured, unpredictable state:

const required = ['DATABASE_URL', 'JWT_SECRET'];
for (const key of required) {
  if (!process.env[key]) {
    throw new Error(`Missing required environment variable: ${key}`);
  }
}

Common mistakes

  • Baking environment-specific values into the image, forcing a separate build per environment and breaking the principle that the same tested artifact is promoted unchanged through every stage.
  • Storing credentials in plain environment variables when a dedicated secrets mechanism is available, exposing them through docker inspect or process listings.
  • Committing a production .env file to version control, permanently leaking whatever secrets it contained at the time of the commit.
  • Omitting live-restore and similar daemon settings, causing every container on a host to stop whenever the Docker daemon is restarted for an unrelated reason such as a daemon upgrade.
  • Allowing an application to start successfully with missing configuration and fail confusingly later, instead of validating required configuration immediately at startup.

Production configuration for Docker is fundamentally about externalizing everything that varies by environment, keeping secrets on a different handling path than ordinary settings, and applying the same daemon-level and resource-level rigor to the runtime environment that is applied to the application configuration itself.

Content in this section