# What a Dockerfile review should catch

> A Dockerfile can leak a secret, run as root, break the build cache or ship build tools. Seven checks a reviewer can run by reading the file, with safe forms.

Published 2026-10-27 · https://aiskills402.com/blog/dockerfile-review-checklist

A Dockerfile review should catch anything that leaks a secret into the image, gives the running container wider privileges than its job calls for, makes the build impossible to repeat, or wastes time and space. All of that is visible in the file itself, before a single layer is built. This guide lists seven checks in the order that pays off most, with what the safe version looks like, so you can review a Dockerfile in a few minutes or hand the same list to a colleague.

## 1. Who the container runs as

If nothing says otherwise, the main process starts as the root user. OWASP names a user without privileges as the strongest guard against escalation, and shows the pattern: create a user in the Dockerfile, do the root-only work such as installing packages first, then switch with `USER` ([OWASP cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html)). Docker's own guidance says the same for any service able to work without elevated rights ([Docker docs](https://docs.docker.com/build/building/best-practices/)).

The thing to look at is the last stage of the file. Root in an early build stage that is thrown away is harmless; root in the stage that becomes the image is the finding.

## 2. Which base image, exactly

`FROM node` or `FROM ubuntu:latest` means "whatever the publisher decides today". Docker explains that tags are mutable: a publisher can point a tag at a new image at any time, which is convenient for updates and bad for repeatability ([Docker docs](https://docs.docker.com/build/building/best-practices/)). A specific version tag is the minimum. Pinning a digest as well makes the base completely fixed, with a cost that the same page spells out: you opt out of automatic security fixes unless something updates the digest for you, and it suggests Dependabot for that.

A reviewer should accept either a version tag or a tag plus digest, and flag an untagged or `latest` base in a stage that ships.

## 3. Secrets that end up in a layer

Anything copied, set or passed during the build can be recovered from the image later. The classic forms are a token in `ARG`, a password in `ENV`, and a `COPY` of a `.env` file or a key. Deleting the file in a later `RUN` does not help, because the earlier layer still holds it. OWASP's advice is to keep sensitive data out of images and runtime commands and to use proper secret handling instead ([OWASP cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html)).

The safe look-alike is `RUN --mount=type=secret`: the value exists only while that single instruction runs and is never written into the image. A file supplied that way should not be flagged.

## 4. The order of instructions and the build cache

Docker walks the instructions top to bottom and, for each one, checks whether it can reuse the stored result ([Docker docs](https://docs.docker.com/build/building/best-practices/)). Once one instruction changes, everything after it is rebuilt. So the file should copy the dependency manifest first, install dependencies, and only then copy the rest of the source. If all the source is copied ahead of the install, every edit to any file reinstalls every dependency, which is the typical cause of a build that feels slow for no reason.

This check often costs nothing to fix and saves the most minutes over a year of builds. A model asked to review code tends to be cautious about what it reports, a habit we looked at in [a note on safe defaults in code review](/blog/llm-code-review-safe-default), and it applies here: a reviewer should still say plainly when the order is wrong.

## 5. Package lists left in the layer

On Debian-based images the package index is downloaded by `apt-get update`. Docker's example combines the update, the install with `--no-install-recommends`, and the removal of the index directory in one `RUN`, so the downloaded lists never become part of a layer ([Docker docs](https://docs.docker.com/build/building/best-practices/)). The docs also advise against adding packages out of habit: fewer packages mean fewer dependencies, a smaller image and faster builds.

Two separate `RUN` lines, one for the update and one for the install, are a finding for a second reason: a cached update step can go stale while the install runs against old lists.

## 6. ADD versus COPY, and what gets sent as context

`COPY` copies files from the build context or from another stage. `ADD` does that and can also fetch remote URLs and unpack archives automatically, which makes it surprising to read ([Docker docs](https://docs.docker.com/build/building/best-practices/)). Prefer `COPY`, and keep `ADD` for the cases that truly need the extra behavior, with a comment explaining the choice.

The context itself matters too. Without a `.dockerignore`, the whole directory is sent to the builder, including version-control history, local environment files and dependency folders. Docker's guide shows the file as the way to leave out what the build does not need, with patterns like those in `.gitignore` ([Docker docs](https://docs.docker.com/build/building/best-practices/)). A missing ignore file is both a speed problem and a secret-leak risk, because a broad `COPY . .` will then take the local `.env` along.

## 7. Multi-stage builds

Compilers, test tools and package caches belong in a build stage, not in the image people run. Multi-stage builds separate building from the final output, so the result contains only the files needed to run the application ([Docker docs](https://docs.docker.com/build/building/best-practices/)). The reviewer's question: does the final `FROM` start from a clean base and copy only artifacts from earlier stages with `COPY --from`? A single-stage file that installs a toolchain and then runs the app is the finding.

This check also supports the others: root, secrets and build tools in an earlier stage are much less serious when that stage does not ship.

## A reading order that works

1. Find the last `FROM`. That stage is the image.
2. Check its base reference and the final `USER`.
3. Search the whole file for `ARG`, `ENV` and `COPY` lines that can carry a credential.
4. Read the order: manifest, install, then source.
5. Look at every `apt-get` line.
6. Confirm that a `.dockerignore` exists next to the file.

## Where our skill fits

Our skill [dockerfile-review](https://aiskills402.com/skills/dockerfile-review) reads one Dockerfile and lists a short set of mistakes: an image that ships as root, a base with no tag or the moving one, secrets passed by argument, environment value or copied file, source copied ahead of the install, and a package index left behind. Every line it prints names the instruction, the reason and the fix, and an empty result is stated plainly. It leaves the safe look-alikes alone, such as a mounted build secret or root in an early stage that is discarded. Use it before merging a change, or to find out why a build is slow. The whole store is at https://aiskills402.com.

## When this does not apply

- **The file is only half the picture.** What the container is given at run time, such as capabilities, mounted volumes, the network and resource limits, is outside the Dockerfile. A perfect file can still run with too much.
- **Orchestrator settings override it.** A Kubernetes pod or Compose file can set the user, mount secrets or change the command, so check those as well before judging the whole system.
- **Some images need root.** A few tools bind low ports or manage devices. The finding then becomes a question to answer in a comment, not an automatic error.
- **Pinning has a price.** A digest keeps builds identical, but you must update it yourself, and an old pinned base can carry known flaws. Judge by how often someone will actually bump it.
- **A review does not scan the contents.** Reading the file will not tell you whether the packages inside have known vulnerabilities; that needs an image scanner.
- **Our skill is a reader, not an oracle.** Its reading can skip a rare pattern or flag something innocent, and it checks a fixed list. Treat its output as input to your decision and rebuild after changes to see the effect.
