A Dockerfile review should assess more than whether an image builds. This reusable checklist helps teams improve build speed, reduce Docker image size, limit security exposure, and keep container behavior predictable before merging changes.
Overview
A Dockerfile is both a build recipe and part of a software supply chain. Small choices about the base image, instruction order, build context, package installation, and runtime user can affect build time, cache reuse, image size, portability, and operational risk.
Before approving a Dockerfile, review it in four dimensions:
- Build efficiency: Can the builder reuse unchanged work, and is unnecessary data excluded from the build context?
- Runtime contents: Does the final image contain only what the application needs to run?
- Security: Are packages, credentials, permissions, and image dependencies handled deliberately?
- Reproducibility: Can another developer or CI job produce a predictable result from the declared inputs?
Use the checklist below as a pull-request review aid rather than a one-time optimization exercise. A smaller image is useful, but it should not come at the cost of an unclear build or a fragile runtime. For related workflow controls, pair this review with a developer workflow automation checklist and a CI/CD review that makes image builds and scans repeatable.
Checklist by scenario
1. Starting or changing the base image
- Confirm that the base image provides the operating-system libraries and runtime features the application actually needs.
- Prefer a maintained, purpose-appropriate image over a convenient but unexplained choice.
- Choose a smaller runtime variant when it is compatible with the application and support requirements.
- Record why the selected image and version are appropriate. Avoid relying on an unqualified moving tag when repeatable builds matter.
- Check whether native dependencies, debugging tools, certificates, time-zone data, or shell behavior are required. Do not remove them solely to reduce bytes.
2. Improving build speed and cache reuse
- Copy dependency manifests before copying the rest of the source tree when the package manager can install from those manifests.
- Place stable instructions before frequently changing instructions. Source edits should not invalidate expensive dependency-installation steps unnecessarily.
- Combine related package-manager operations where appropriate, and clean package indexes in the same layer if they are not needed at runtime.
- Use the build context deliberately. A focused context and a useful
.dockerignorefile can exclude repositories, local dependencies, test output, caches, credentials, and editor files. - Consider BuildKit features used by the team, such as cache mounts, but document assumptions so the Dockerfile still has a clear fallback behavior.
3. Reducing Docker image size with a multi-stage build
- Separate compilation, testing, and packaging from the production runtime.
- Use a builder stage for compilers, headers, package managers, and other build-only tools.
- Copy only the executable, application bundle, static assets, and runtime libraries required by the final process.
- Inspect the final image rather than assuming that a multi-stage build removed every unnecessary file.
- Keep stage names descriptive, such as
build,test, andruntime, so future changes are easier to review.
FROM example-language:build AS build
WORKDIR /src
COPY dependency-manifest* ./
RUN install-dependencies
COPY . .
RUN compile-application
FROM example-language:runtime AS runtime
WORKDIR /app
COPY --from=build /src/output ./output
USER app
CMD ["./output/service"]
The exact commands vary by language, but the principle is stable: build tools belong in the builder stage, while the runtime stage should contain the smallest complete environment that can run the service.
4. Reviewing security controls
- Do not place passwords, API keys, private keys, or tokens in Dockerfile instructions, build arguments, copied files, or image layers.
- Use the team’s approved secret-handling mechanism for build-time and runtime credentials, and verify that secret files are excluded from the context.
- Run the application as a non-root user unless a documented requirement makes that impractical.
- Create or select the runtime user explicitly, set ownership for copied files, and confirm that the process can write only where it needs to.
- Scan the resulting image and review findings according to the project’s risk process. A scanner result is an input for investigation, not a substitute for understanding the dependency.
- Pin or otherwise control dependency versions where reproducibility and change review require it, then define how updates will be made.
5. Supporting CI/CD and operations
- Build the same Dockerfile in CI that developers use locally, rather than maintaining an undocumented alternate packaging path.
- Test the image as a container: verify its entrypoint, listening address, required environment variables, file permissions, and graceful shutdown behavior.
- Keep health checks and orchestration settings in the appropriate layer of the platform. A Dockerfile should not conceal operational assumptions.
- Publish image metadata that helps identify the source revision and build, subject to the project’s conventions.
- Make failures visible before deployment by adding linting, image inspection, vulnerability scanning, and a basic startup test to the pipeline.
What to double-check
Several Dockerfile details are easy to overlook during a quick review:
- Instruction order: Ask which files change most often. Put those inputs later when doing so preserves useful cache entries.
- Copy scope: Read every
COPYandADDinstruction. Confirm that it brings in only intended files and does not silently include local secrets or large artifacts. - Layer cleanup: Removing a file in a later instruction may not remove it from earlier layers. Prevent unwanted data from entering the image rather than trying to delete it afterward.
- Runtime user: Check the effective user, file ownership, writable directories, and behavior when the container runs with a read-only root filesystem if that is part of the deployment model.
- Entrypoint behavior: Confirm that the main process receives signals correctly and remains the container’s primary process. Shell wrappers can change signal and exit-code behavior.
- Package cleanup: Verify that package indexes, compiler caches, test fixtures, documentation, and temporary files are not carried into the runtime stage without a reason.
- Architecture assumptions: If images are built for more than one platform, test native dependencies and compiled artifacts for each supported target.
When an image is used by a Kubernetes workload, review the Dockerfile alongside the deployment configuration. The image, security context, probes, resource settings, and volume permissions should describe one coherent runtime contract. The Kubernetes developer workflow guide can help structure that broader review.
Common mistakes
- Using a large development image in production: Development tools may be useful for local work but unnecessary in the deployed image. Use a multi-stage Docker build or a separate development target.
- Copying the entire repository too early: This commonly invalidates dependency-installation caches whenever any source or documentation file changes. Copy manifests first where the package manager supports it.
- Putting secrets in build arguments: Build arguments can become visible through build history or other metadata. Treat them as unsuitable for sensitive values unless the build system provides a deliberate secret mechanism.
- Assuming “latest” is reproducible: A moving tag can change without a Dockerfile diff. Use an update process that makes base-image changes visible and testable.
- Optimizing size without testing: Removing shells, certificates, libraries, or user data can break diagnostics or application behavior. Measure the result and run the actual workload.
- Installing packages without cleanup: Package indexes and temporary build files can add weight when they are created in a layer that remains in the final image.
- Running as root by default: This may hide permission problems during development and increase the impact of a compromised process. Define the runtime identity explicitly.
- Relying only on a scanner: Scanners can identify packages that deserve attention, but teams still need ownership, upgrade decisions, exception records, and runtime testing.
When to revisit
Revisit this Dockerfile review checklist whenever a service changes its base image, language runtime, package manager, build system, or deployment target. It is also worth repeating when a dependency upgrade introduces native compilation, when an image becomes noticeably slower to build, or when its size grows without an obvious feature-level reason.
As a practical team habit, run a lightweight review for every Dockerfile pull request and a broader image review during planned maintenance or seasonal planning cycles. Recheck the build context, cache behavior, runtime user, secret path, scanner output, and final image contents. If the project adopts new BuildKit behavior, CI/CD tooling, or Kubernetes security settings, update the checklist and its examples so they match the workflow people actually use.
Before merging, ask one final question: Could another engineer explain what is in this image, why it is there, how it was built, and which user runs it? If the answer is unclear, pause the merge, inspect the image and build logs, and document the missing decision. This small review step helps keep Docker image optimization and container security part of normal DevOps practice rather than emergency cleanup.