Dockerfile check · medium

Unpinned base image (FROM … :latest)

FROM node with no tag means node:latest, and latest is just a tag the publisher moves to each new release. Two builds of the same Dockerfile can start from different base images, so what you tested is not necessarily what you ship, and a major version bump or a bad upstream release reaches production without anyone reviewing it.

$ explain Dockerfile
! Base image node is not version-pinned medium
line 1
fix: see below
$ 

What the free Dockerfile explainer reports on the risky example below.

Why it matters

latest moves whenever the publisher pushes a new release, including major versions with breaking changes. A rebuild for an unrelated fix can silently change the runtime, the OS packages and the default user under your app.

Without a version in the Dockerfile you cannot tell from the repository which base an image was built on, which makes it harder to reproduce a build or to know whether an advisory affects you.

Pinning makes base changes explicit: an update becomes a one-line diff that goes through review and CI like any other change.

Risky and safer Dockerfile

RiskyDockerfile
FROM node:latest
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY src ./src
USER 10001
HEALTHCHECK CMD ["node", "src/healthcheck.js"]
CMD ["node", "src/server.js"]
SaferDockerfile
FROM node:22.11-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY src ./src
USER 10001
HEALTHCHECK CMD ["node", "src/healthcheck.js"]
CMD ["node", "src/server.js"]

How to fix it

  • Replace latest (or a missing tag) with a specific version tag, e.g. node:22.11-slim.
  • For an immutable reference, add the digest too: node:22.11-slim@sha256:<digest>.
  • Let Renovate or Dependabot open pull requests for base image updates so you still get security fixes, through review.

When it's acceptable

  • Scratch experiments and throwaway images where reproducibility doesn't matter.
  • The rule skips FROM scratch, references to an earlier build stage, and image names built from an ARG, since those are pinned (or not) somewhere else.

Check your own Dockerfile

The risky example is loaded below. Paste your own file to check it for this and every other Dockerfile rule. It runs in your browser; nothing is uploaded.

Dockerfile

Parsed in your browser. Nothing is uploaded.

Explanation

1 stage8 instructions1 medium

Stage 0node:latest

  • Docs for FROM (opens in a new tab)

    Starts from the base image node, tag latest.

    The latest tag (or no tag) can point at a different image on every build, so what you tested is not what you ship and upstream changes reach production unreviewed.

    Safer: Pin a specific version tag, and ideally the digest too, e.g. image:1.27@sha256:<digest>, and let Renovate or Dependabot propose bumps.

  • Docs for WORKDIR (opens in a new tab)

    Sets the working directory to /app for later instructions and the container. It is created if it does not exist.

  • Docs for COPY (opens in a new tab)

    Copies package.json, package-lock.json from your build context to ./ in the image.

  • Docs for RUN (opens in a new tab)

    Runs npm ci --omit=dev while building the image and saves the result as a new layer.

  • Docs for COPY (opens in a new tab)

    Copies src from your build context to ./src in the image.

  • Docs for USER (opens in a new tab)

    Runs later instructions and the container's process as user 10001.

  • Docs for HEALTHCHECK (opens in a new tab)

    Runs node src/healthcheck.js to check the container is healthy.

  • Docs for CMD (opens in a new tab)

    Sets the default command to node src/server.js. It runs when the container starts, and anything passed to docker run replaces it.

Questions

What does FROM image without a tag use in a Dockerfile?

It uses the latest tag. latest is not special or necessarily the newest stable release; it is whichever image the publisher last pushed under that tag.

Should I use the latest tag in production Dockerfiles?

No. Pin a version tag so builds are repeatable and base changes go through review, and ideally pin the digest as well.

How do I keep a pinned base image up to date?

Use a dependency bot such as Renovate or Dependabot, which can open a pull request when a newer tag or digest is published, so updates are deliberate and tested.