Docker guide

Docker Production Best Practices

A container that starts once is not the same as a container that stays up. These practices make Docker images smaller, safer, and honest about what happens when a process fails.
11 min readUpdated 2026-08-12

Search focus

Docker best practicesmulti-stage buildsDocker securitycontainer image scanningDocker production
Published 2026-08-12Updated 2026-08-12CloudOpsync

Start from a minimal, maintained base

The base image determines most of your attack surface and disk footprint. Prefer a minimal, actively maintained base such as a recent Debian or Alpine image for a small surface, or the official runtime image for your language, and note the difference between glibc and musl variants when you depend on native modules. Inspect the image and run a scanner as part of the build, and read the distribution's security and update stream before fixing a tag. Distroless images reduce the surface further but make debugging harder, so balance what you need: a scratch final stage holding a static binary is a legitimate choice, and shipping a full operating system image without minimising it is a choice you should make on purpose, not by habit.

Use multi-stage builds

A multi-stage Dockerfile builds the artifact in one stage and copies only what is needed into the final image. Build with the full toolchain in the first stage, then copy the runtime files with a copy stage instruction, dropping the compiler, test runners, and package managers from the final layers. This is the single highest-impact habit: the resulting image is smaller, faster to pull, and exposes less attack surface. Name each stage clearly, keep the final stage as the default target, and use buildkit features for cross-architecture builds when your stack supports them. You can even run tests in a builder stage before the final image is assembled. Cache deliberately: copy dependency manifests before source so package install steps reuse the build cache instead of rerunning.

Run as a non-root user

Code inside a container runs on the host kernel, and root inside a container still maps to kernel capabilities once the process escapes its namespaces. Create a dedicated user in the image and switch to it with the USER instruction, then make the filesystem and the writable paths the process actually needs owned by that user. Avoid privileged mode and grant only the capabilities required, dropping the rest. Running as non-root exposes bugs immediately, such as a volume mounted without permission or a service trying to bind a privileged port, which is exactly why the failures must surface in staging and not in production. For workloads that wrap setuid or privileged helpers, evaluate what the process genuinely needs and grant exactly that and no more.

Make the filesystem read-only where possible

Set the root filesystem to read-only at runtime and mount a small writable volume at the exact paths that need writes, such as a temporary directory or a cache, so a compromised process cannot plant files on the host. Image layers are already immutable, but a read-only root filesystem extends that guarantee to runtime behaviour. If the application writes configuration or cache, mount one small writable volume for that path instead of granting the whole filesystem write access. Combine a read-only root with a non-root user and dropped capabilities for the strongest baseline, and expect some applications to need small accommodations, such as a cache directory or a writable temporary path. Test the read-only flag in CI before shipping, because an application that writes hidden state will fail loudly when you flip it.

Provide health checks and honest stop behavior

A HEALTHCHECK in the Dockerfile, or a health command for Compose and orchestrators, gives the platform a signal about whether the process actually serves traffic. Point the probe at a cheap endpoint that reflects real application state rather than a static page that always answers. Stop behaviour matters equally: the runtime sends SIGTERM on stop, so your process must handle it, finish in-flight work, and exit, and the orchestrator must allow a grace period for that. Document any process that needs a distinct stop mechanism. Use restart policies for transient failures, but pair restarts with health checks and alerting so a crashing application cannot loop silently forever. Sometimes the kindest production setting is stop and page an operator instead of retrying endlessly.

Pin images by digest, review tags

A tag like the latest can change under you when the distribution ships a point release, which makes your tested image no longer exactly what you tested. For production deployments, reference the exact digest, image@sha256..., in the runtime manifest and the deployment record, while the Dockerfile may keep a semantic tag plus a lockfile approach. This gives reproducible pulls and an audit trail for what actually ran. Where your pipeline builds images, record the digest in the release metadata and use it for promotion between environments. Review upstream tag drift with periodic image updates, and treat digest pinning as the default for anything that reaches production, with a deliberate, reviewed exception for base-image maintenance windows.

Scan images and sign releases

Scanning catches known vulnerabilities in operating system packages and application dependencies at build time. Integrate a scanner such as Trivy or Snyk into the pipeline and fail the build on vulnerabilities above your policy threshold, while providing a path for the exemptions you can genuinely justify. Sign the image so consumers can verify provenance and confirm the digest matches the signer. A signed image is a claim about who built it and from what source, which matters when a registry account or a base image goes bad. Generate a software bill of materials next to the images you ship, so the team can answer what is in this build within hours during a vulnerability investigation rather than days.

Set resource limits and contain restart loops

A container that leaks memory will eventually starve its neighbours, so set memory limits that match a realistic worst case and CPU limits for the workload, and give the orchestrator enough information to schedule without collisions. A limit that is too small makes the platform kill a healthy process, while a limit that is too generous lets neighbours starve each other. Track what the container really consumes and adjust limits from observed production data. Apply the same discipline to restarts: a web tier that crash-loops silently fools nobody with a health dashboard, so alert on restart counts and container state. Match the number of replicas to your load profile and tuning so you are scaling deliberately rather than breeding a restart storm you only notice in the bill.

Route logs to stdout and stderr

Processes inside a container should write logs to stdout and stderr rather than to files, because the platform collects those streams. Docker captures standard output and the log driver ships it, while an application that writes to a log file inside the container is invisible unless you also run a sidecar. Structure log lines as JSON so grep and the aggregator can parse them, and keep filenames out of the log text where possible. Configure rotation at the platform level so a log file never fills the container's disk. Add a correlation identifier to every line of a request so tracing across services starts at the log line. The goal is that every log reaches a place you can search months after an incident starts.

Manage image lifecycle in the registry

Registries accumulate cost if they are not managed. Set retention for untagged and old images after a few weeks, name images by commit or build identifier, and keep environment promotion at the digest level so staging pushes the same image as production. Protect the image namespace so the deploy pipeline reads only from the reviewed image set. Every tag is storage and every pull is bandwidth, so clean up as you go and keep only the history you actually need for rollback, typically a handful of versions. Set a base-image update cadence as a lifecycle decision rather than a fire-time choice. A clean registry answers three questions in a security review without stress: what ran, when, and from what source.

Implementation checklist

Turn the article into a safer production change.

  1. Step 1

    Clarify the production goal behind docker production best practices and the business risk it should reduce.

  2. Step 2

    Review the current stack, deployment process, infrastructure ownership, monitoring, security, and support gaps.

  3. Step 3

    Prioritize the smallest useful change that improves reliability, automation, visibility, or recovery.

  4. Step 4

    Validate the change with logs, health checks, rollback notes, and a handover your team can keep using.

People also ask

What is the main takeaway from Docker Production Best Practices?

Multi-stage builds and slim, maintained base images shrink the attack surface and the pull time in one step.

When should a team apply this docker guide guidance?

Pin images by digest, scan and sign every release, and route logs to stdout so what is running is provable and observable.

Consultation

Turn the guide into a production-ready DevOps plan.

Share your stack, risk level, and delivery goal. You will get a practical scope conversation instead of a generic sales pitch.