Dockerfile Best Practices Checklist: Build Smaller, Safer, More Reliable Images
DockerContainersDevOpsCI/CDApplication Security

Dockerfile Best Practices Checklist: Build Smaller, Safer, More Reliable Images

WWebTechWorld Editorial Team
2026-11-08
8 min read

Use this Dockerfile checklist to build smaller, safer images with better caching, secret handling, runtime security, and deployment reliability.

A production-ready Dockerfile is more than a set of commands that builds successfully. It should produce a predictable, appropriately sized image, avoid exposing credentials, run with limited privileges, and provide enough health and runtime information for your deployment platform to operate it safely. This Dockerfile best practices checklist gives you a repeatable review workflow for application images, whether you deploy to a managed platform, virtual machine, or cloud container service.

Overview

Improving a Dockerfile for production starts with separating build concerns from runtime concerns. Developers need compilers, package managers, test tools, and source files during a build; the running application usually needs only its compiled output, production dependencies, configuration, and a minimal set of operating-system files.

Review the Dockerfile in this order:

  1. Build context: Confirm that unnecessary files are excluded with a suitable .dockerignore.
  2. Base image: Choose a maintained image that matches the application runtime and operational requirements.
  3. Build stages: Use multi-stage builds when compilation or asset generation is not required at runtime.
  4. Layer design: Copy dependency manifests before application source when that improves build-cache reuse.
  5. Security: Keep secrets out of image layers and run the application as a non-root user where practical.
  6. Runtime behavior: Define a clear command, configuration boundary, logging approach, and health-check strategy.
  7. Verification: Inspect image size, scan for vulnerabilities, test the image as it will run, and document exceptions.

A smaller image is not automatically a safer image, and a successful build is not proof that the container is production-ready. Treat optimization, security, and operability as separate review areas.

Checklist by scenario

For every production Dockerfile

  • Use a deliberately selected base image rather than an unexplained default.
  • Record the runtime or base-image version in a way that supports repeatable builds. A moving tag may be convenient, but it can change the result of a later build.
  • Add a .dockerignore file that excludes source-control metadata, local dependencies, build output, logs, environment files, editor settings, and test artifacts that are not needed in the image.
  • Keep application configuration in environment variables or a platform configuration system instead of baking deployment-specific values into the image.
  • Make the container's main process explicit with CMD or ENTRYPOINT.
  • Ensure the application writes logs to standard output and standard error unless there is a deliberate, documented alternative.
  • Run a vulnerability scan and investigate findings rather than treating the scan as a pass-or-fail substitute for review.

When building a compiled application or front-end bundle

Use a multi-stage Docker build so the compiler and development dependencies remain in a builder stage. The final stage should copy only the files required to serve or run the application.

# syntax=docker/dockerfile:1
FROM node:22-bookworm AS build
WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM nginx:stable
COPY --from=build /app/dist /usr/share/nginx/html

The exact runtime image depends on the application. A static front-end may need only a web server, while a server-rendered application may need a language runtime and production-only packages. Check the output path, ownership, startup command, and web-server configuration before assuming that a builder-to-runtime copy is sufficient.

When deploying a server application

  • Install production dependencies separately from development dependencies where the package manager supports it.
  • Copy lockfiles and manifest files before source code to make dependency layers easier to reuse.
  • Use a multi-stage build if native modules, compilers, or test tooling are needed only during installation or compilation.
  • Set a working directory and use an explicit, non-root runtime user.
  • Confirm that the application listens on the expected interface and receives its port through configuration when required by the hosting platform.
  • Handle termination signals correctly so the process can shut down cleanly during deployment or scaling operations.
FROM python:3.12-slim AS runtime
WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

COPY --from=build /opt/venv /opt/venv
COPY app ./app

RUN useradd --create-home --uid 10001 appuser \
    && chown -R appuser:appuser /app
USER appuser

CMD ["/opt/venv/bin/gunicorn", "app.wsgi:application"]

This example assumes a separate build stage created the virtual environment. Adapt the command and paths to the framework rather than copying it unchanged.

When reducing image size

  1. Measure the current image before changing it.
  2. Inspect which layers contain the largest files.
  3. Remove build tools from the final stage through multi-stage builds rather than deleting them in a later layer.
  4. Install only the packages the running process needs.
  5. Combine related package-manager operations where appropriate and clean package metadata according to the base image's conventions.
  6. Do not remove diagnostic tools that are required for safe operation without providing an alternative debugging method.

Layer deletion does not necessarily reduce the stored image when the unwanted files were introduced in an earlier layer. Preventing unnecessary files from entering the final stage is usually clearer and more effective.

What to double-check

Base images and pinning

Choose an image with a clear maintenance process, a runtime version compatible with the application, and a package set appropriate for the workload. Decide how much reproducibility you need. A version tag is readable and easy to update; a digest can make the exact source immutable, but it adds an update task. Whichever approach you use, record how the image is reviewed and refreshed.

Check the image's operating-system family, architecture support, package availability, and documented support window before committing to it. Avoid choosing a minimal variant solely because it has fewer packages; compatibility and debuggability also matter.

Dependency caching and layer order

Place infrequently changing inputs before frequently changing inputs. For example, copy a package manifest and lockfile, install dependencies, then copy application source. A source-code edit can then avoid repeating dependency installation when the relevant build cache remains available.

Use BuildKit features, such as cache mounts or secret mounts, when they are supported by your build environment and understood by your team. A cache can improve build speed, but it should not be required for correctness. A secret mount should prevent credentials from being written into an image layer; verify the resulting image rather than assuming the syntax alone guarantees safe handling.

Secrets and sensitive files

Never place API keys, private certificates, passwords, or production environment files in COPY instructions, build arguments, or committed Dockerfiles. Build arguments can be visible in build history or metadata depending on the tooling and should not be treated as a secure secret store. Supply sensitive values through the CI system or deployment platform using its documented secret mechanism.

After a build, inspect image history, exported layers, and the final filesystem for accidental credentials. If a secret has entered an image or repository, remove the exposure and rotate the credential; deleting the line in a later commit is not enough.

Users, permissions, and health

Use a dedicated non-root user for the application when the runtime and hosting environment allow it. Make only the required directories writable, and verify that the application can read its files and write to any intended temporary or cache location.

A health check should test a meaningful condition, such as the application responding on its internal health endpoint. It should be lightweight, have reasonable timing, and distinguish application readiness from simple process existence when the deployment platform supports separate readiness and liveness concepts. Do not add a health check that depends on a tool absent from the final image.

Common mistakes

MistakeLikely effectBetter review question
Using one large image for both build and runtimeMore packages, larger transfers, and a wider attack surfaceCan the runtime stage contain only compiled output and production dependencies?
Copying the entire repository before installing dependenciesFrequent source changes invalidate dependency-cache layersCan manifests and lockfiles be copied first?
Running as root by defaultA process compromise may have broader consequences inside the containerCan a dedicated user run the application without elevated privileges?
Embedding secrets in the DockerfileCredentials may persist in history, layers, logs, or source controlCan the value be provided only at build or runtime through a secret mechanism?
Using a floating base-image tag without an update processBuild results can change unexpectedly or updates can be missedWho reviews base-image changes and how are they tested?
Adding a health check that tests the wrong thingHealthy processes may be marked unavailable, or broken applications may appear readyDoes the check represent the service's actual readiness?
Optimizing size without testing behaviorRequired certificates, shells, libraries, or diagnostic capabilities may be removedDoes the final image pass startup, request, shutdown, and monitoring tests?

Another frequent mistake is assuming that Dockerfile security can be solved entirely inside the file. The build pipeline, registry permissions, host configuration, runtime privileges, network policy, dependency process, and secret-management system all affect the final risk. Review the image as one part of the deployment workflow. For broader deployment planning, see the practical guide to hosting, CI/CD, domains, and rollbacks.

When to revisit

Run this checklist before a production release, after a major dependency or language-runtime upgrade, and whenever the CI/CD pipeline or hosting platform changes. It is also worth revisiting after a vulnerability report, a change in application architecture, a move between CPU architectures, or a decision to add background workers, scheduled jobs, or additional service ports.

For a repeatable review, save the following with the project:

  • the Dockerfile and .dockerignore review date;
  • the selected base-image reference and update owner;
  • the built image size and the largest layers;
  • scanner findings, accepted risks, and remediation dates;
  • the user, exposed ports, startup command, and health-check behavior;
  • test results for startup, normal requests, graceful shutdown, and configuration errors.

Before approving the next image, build it from a clean environment, scan the resulting artifact, run it with production-like configuration, and confirm that no credentials or unnecessary source files are present. Then compare the result with the previous image. This simple before-and-after record makes image-size growth, new vulnerabilities, and behavior changes easier to detect during routine deployment work.

Dockerfiles should be updated when their inputs change, not only when a release is blocked. Pair this checklist with your deployment and performance reviews, including the comparison of production hosting options and the web performance optimization checklist, so the container remains aligned with the way the application is actually operated.

Related Topics

#Docker#Containers#DevOps#CI/CD#Application Security
W

WebTechWorld Editorial Team

Technology Editors

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.