Docker Production Best Practices

Run Docker in production reliably: pinned images, health checks, resource limits, logging, secrets, graceful shutdown, and a deployment checklist.

Running a container on a laptop is easy; running it reliably in production requires deliberate choices about images, configuration, resources, observability, and shutdown behaviour. This lesson collects the practices that matter most, tying together earlier lessons on Dockerfiles, security, networking, volumes, and registries into a production checklist.

Build Reproducible, Minimal Images

  • Pin base images to a specific version (eclipse-temurin:21-jre, postgres:16), and consider digests for strict reproducibility.
  • Use multi-stage builds so the runtime image has no compilers or build tools.
  • Tag images with an immutable identifier (version or commit SHA) and never deploy latest.
  • Rebuild images regularly to pick up patched base layers, and scan them in CI.
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /workspace
COPY pom.xml .
RUN mvn -B -q dependency:go-offline
COPY src ./src
RUN mvn -B -DskipTests package

FROM eclipse-temurin:21-jre
RUN groupadd --system app && useradd --system --gid app --no-create-home app
WORKDIR /app
COPY --from=build --chown=app:app /workspace/target/*.jar app.jar
USER app
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "-jar", "/app/app.jar"]

Make the Container Behave Well at Runtime

Treat containers as stateless and disposable

Keep persistent data in volumes or external services (databases, object storage), and keep configuration outside the image.

Configuration through environment

Follow the twelve-factor approach: one image, different configuration per environment via environment variables, config files, or secrets.

Graceful shutdown

docker stop sends SIGTERM, then SIGKILL after the grace period (10 seconds by default). Your process must be PID 1 (exec form) and handle SIGTERM by finishing in-flight requests.

services:
  api:
    image: myorg/api:1.4.2
    stop_grace_period: 30s

Use init: true (or docker run --init) to add a minimal init process that forwards signals and reaps zombie processes when your app spawns child processes.

Health checks

HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
  CMD curl -fsS http://localhost:8080/actuator/health || exit 1

Health status appears in docker ps and can be used by Compose (condition: service_healthy) and orchestrators.

Set Resource Limits

Without limits, a single container can starve the host.

services:
  api:
    image: myorg/api:1.4.2
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 768M
    pids_limit: 300
docker run -d --memory 768m --cpus 1 --pids-limit 300 myorg/api:1.4.2

Size runtime memory flags (such as the JVM heap) to fit within the container limit with headroom for non-heap memory.

Restart Policies

Policy Behaviour
no Never restart (default)
on-failure[:N] Restart only on non-zero exit, optionally up to N times
always Always restart, including after daemon restart
unless-stopped Like always, but not if you stopped it manually

For single-host deployments unless-stopped or on-failure is typical. Restart policies are not a substitute for fixing crash loops.

Logging and Monitoring

  • Write logs to stdout and stderr; do not write log files inside the container.
  • Limit log growth with the json-file driver options or ship logs to a central system.
  • Export metrics (for example Prometheus endpoints) and monitor docker stats-style data such as CPU, memory, restarts, and health.
services:
  api:
    image: myorg/api:1.4.2
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Secrets and Configuration

  • Never bake secrets into images or commit them to Git.
  • Use secret mounts at build time and secret files or a secret manager at runtime.
  • Make configuration files read-only mounts where possible.

Network and Host Hardening

  • Publish only required ports; bind internal services to 127.0.0.1 or keep them on private networks.
  • Put a reverse proxy or load balancer in front of application containers and terminate TLS there.
  • Run non-root, drop capabilities, enable no-new-privileges, and consider a read-only root filesystem (see the Docker Security lesson).
  • Keep the host OS and Docker Engine updated, and restrict access to the Docker socket.

Deployment and Rollback

  • Deploy an explicit tag or digest, so a rollback is just deploying the previous tag.
  • Roll out gradually (rolling or blue-green) where your platform allows it, and gate traffic on health checks.
  • Back up volumes and databases and test restores.
  • For multiple hosts, automatic scaling, and self-healing, use an orchestrator such as Kubernetes (or Docker Swarm) rather than hand-managing containers with Compose.

Production Readiness Checklist

Area Check
Image Pinned base, multi-stage, scanned, non-root, immutable tag
Config Environment-driven, secrets not in image, .dockerignore present
Runtime Memory and CPU limits, HEALTHCHECK, graceful shutdown, --init if needed
Data Volumes for state, tested backups
Logging stdout/stderr, rotation or central shipping
Network Minimal published ports, TLS at the edge, segmented networks
Operations Restart policy, monitoring and alerts, documented rollback

Common Mistakes

  • Deploying latest or an unpinned base image. Behaviour changes without a code change.
  • Running without resource limits. One leak or traffic spike takes down the host.
  • Ignoring signals. Shell-form entrypoints delay shutdown and cause dropped requests.
  • Storing state in the container. Replacing the container loses data.
  • Relying on restart policies to hide crash loops. Monitor restart counts and alert on them.

Best Practices

  • Keep images small, pinned, scanned, and immutable-tagged.
  • Define health checks, limits, and restart policies for every service.
  • Centralize logs and metrics, and alert on health and restarts.
  • Keep configuration and secrets outside the image.
  • Automate builds, scans, and deployments through CI/CD, with a tested rollback path.

Interview Questions

1. What would you check before running a container in production?

Pinned and scanned image, non-root user, resource limits, health check, graceful shutdown handling, externalized configuration and secrets, persistent data on volumes, log rotation, and a rollback plan.

2. Why avoid the latest tag in production?

It is a mutable tag, so two deployments can run different code under the same name. Immutable version or commit-SHA tags (or digests) make deployments reproducible and rollbacks straightforward.

3. How do you ensure graceful shutdown of a containerized app?

Use the exec form of ENTRYPOINT/CMD so the app is PID 1 and receives SIGTERM, handle the signal in the application, and set an appropriate stop grace period (stop_grace_period or docker stop -t). Use --init if child processes need reaping.

4. Why set CPU and memory limits on containers?

They prevent one container from exhausting host resources and make behaviour predictable. A container exceeding its memory limit is OOM-killed, which is preferable to destabilizing the entire host. Applications like the JVM must be configured to respect the limit.

5. How should logging work in containers?

Applications write to stdout and stderr, and Docker's logging driver collects them. Configure rotation (max-size, max-file) or ship logs to a central system, because container filesystems are ephemeral.

6. When is Docker Compose not enough for production?

When you need multi-host scheduling, automatic scaling, rolling updates, and self-healing across nodes. An orchestrator like Kubernetes handles these; Compose suits single-host or small deployments.

7. How do you handle secrets in production?

Keep them out of images, Git, and Dockerfiles. Provide them at runtime through the orchestrator's secret store or a secrets manager, mounted as files or injected securely, and rotate them regularly.

Summary

  • Production containers are minimal, pinned, scanned, non-root, and immutably tagged.
  • Define health checks, resource limits, restart policies, and graceful shutdown.
  • Keep state in volumes, configuration and secrets outside the image, and logs on stdout and stderr with rotation.
  • Plan for deployment, monitoring, and rollback, and use an orchestrator when you outgrow a single host.

Continue Learning

Previous: Docker Troubleshooting

Was this guide helpful?

CodeWithVenu is free to read, with no paywall. If this guide saved you time or helped you prepare, a small voluntary contribution helps me write more practical tutorials, architecture guides, and interview-prep content.