Dockerfile Best Practices: 6 Rules for Production

Dockerfile best practices come down to three goals: a secure image, a small image, and a predictable build.

If you plan to run containers on Kubernetes, these matter even more, because a heavy image slows down every restart when a Pod crashes and gets rescheduled on a node that doesn’t have it cached.

1. Use a small, pinned base image

Choose the smallest base image that runs your app, and pin it to a specific version. Smaller images pull faster and contain fewer packages. alpine or -slim variants are common choices, though Alpine uses musl instead of glibc, which can break some software, so test it locally first.

Avoid latest. It changes without warning, so the same Dockerfile can produce different images next release.

FROM node:22-slim

2. Use multi-stage builds to keep images light

A multi-stage build is a Dockerfile that uses several FROM lines, so you compile in one stage and copy only the result into a clean final stage. Compilers, package caches and source code stay behind, and the final image gets much smaller.

FROM golang:1.23 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app

FROM gcr.io/distroless/static
COPY --from=build /app /app
ENTRYPOINT ["/app"]

3. Run your container as a non-root user

By default, processes in a container run as root. Root makes everything worse. So, create a user and switch to it with the USER instruction.

RUN useradd --create-home appuser
USER appuser

Kubernetes can also enforce this with runAsNonRoot in the Pod security context, so an image that already runs as non-root will work with stricter cluster policies.

4. Order layers to take advantage of the build cache

Docker caches each instruction as a layer, and a change in one layer invalidates every layer after it. Put things that rarely change first, such as dependency installs, and things that change often, such as your source code, last.

COPY package*.json ./
RUN npm ci
COPY . .

With this order, editing a source file doesn’t force a full dependency reinstall. Also combine related commands, like apt-get update and apt-get install, in one RUN line and clean the package cache in the same line.

5. Keep secrets out of your Dockerfile

Never put passwords, tokens or keys in a Dockerfile with ENV, ARG or COPY. Anyone with the image can read its layers, and deleting the file in a later step doesn’t remove it from history. Pass secrets at runtime, for example through Kubernetes Secrets, or use BuildKit secret mounts for build-time needs.

6. Add a .dockerignore file and scan your images

A .dockerignore file stops files like .git, node_modules and local .env files from being sent to the build and copied into the image. It speeds up builds and prevents accidental leaks.

Then scan the finished image for known vulnerabilities with a tool such as Trivy or Grype, ideally in your CI pipeline, so a bad image fails before it ships.

Where to go next

If you want to understand how container runs in a cluster. Read how kube-proxy manages Kubernetes traffic and what changed in Kubernetes v1.32.

If you’re still deciding on a path, see why DevOps is one of the best careers in tech. To automate deployments, try Ansible for an Nginx deployment on Ubuntu.

See ya!


Leave a Reply