2026 Dockerfile Best Practices — Five Things 2020 Got Wrong
Five Dockerfile patterns that were *recommended in 2020* now *work against you in 2026*. What changed.
Five 2020-recommended Dockerfile patterns invert in 2026. The reasons: BuildKit, distroless, multi-arch builds, and supply-chain security tooling have all matured.
Change 1 — combine RUN commands is no longer the answer
2020 recommendation:
RUN apt-get update && apt-get install -y curl jq \
&& rm -rf /var/lib/apt/lists/*Reasoning — each RUN creates a layer; combine to reduce layer count.
2026 — BuildKit supports cache mounts. Layer count itself is less important. Cache efficiency matters more.
RUN --mount=type=cache,target=/var/cache/apt \
--mount=type=cache,target=/var/lib/apt/lists \
apt-get update && apt-get install -y curl jqWith cache mounts, each RUN has its own cache and rebuilds are faster than the combined version.
Change 2 — alpine is no longer the default
2020 — FROM alpine as the lightweight default.
2026 — Alpine's musl libc causes compatibility issues with Python / Node / glibc-based libraries. Debugging time consistently exceeds the size savings.
Recommended — distroless (Google) or chainguard minimal images. glibc compatible and smaller than Alpine.
# 2020
FROM alpine:3.18
# 2026
FROM gcr.io/distroless/nodejs22-debian12
# or
FROM cgr.dev/chainguard/nodeChange 3 — never use latest is partly wrong
2020 — FROM node:latest is forbidden. Use a fixed version.
2026 — latest is still wrong, but pinning by digest has become the standard.
# 2020-recommended (insufficient)
FROM node:20-alpine
# 2026-recommended (digest-pinned)
FROM node:22.11.0-slim@sha256:af16...Why — even the same tag (node:22-slim) can point to different images over time. Defending against supply-chain attacks (SLSA, etc.) requires pinning to the digest.
Change 4 — non-root user is not enough
2020 — switch to non-root via USER appuser.
2026 — non-root alone is insufficient. All five together:
USER appuser
WORKDIR /app
COPY --chown=appuser:appuser . /app
# 2026 additions
COPY --chmod=0444 ./config.json /app/config.json # read-only files
LABEL org.opencontainers.image.base.digest=sha256:...
LABEL org.opencontainers.image.source=https://github.com/...The added LABELs are for supply-chain traceability. Tools like docker scout / trivy / grype read these labels for provenance tracking.
Change 5 — single-stage is no longer justifiable
2020 — multi-stage builds are for complex cases. Simple apps go single-stage.
2026 — every production image is multi-stage. Build tools (gcc, npm, etc.) in the runtime image widen the attack surface. The complexity cost of multi-stage is dwarfed by the attack-surface reduction.
# Build stage
FROM node:22-slim AS build
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
RUN npm run build
# Runtime stage
FROM gcr.io/distroless/nodejs22-debian12
WORKDIR /app
COPY --from=build /app/dist /app/dist
COPY --from=build /app/node_modules /app/node_modules
CMD ["dist/index.js"]The runtime image contains no npm at all. The attack surface drops by dozens of build tools.
What the five share
The five share one thing — priorities have shifted. In 2020, image size and simplicity led. In 2026, supply-chain security, build-cache efficiency, and compatibility lead.
Dockerfile best practices change every six years. 2020's "best" can be 2026's "worst".
Do Dockerfiles written to the 2020 guide need fixing now?
A Dockerfile written to the 2020 guide that hasn't adopted three or more of the five changes above is likely to draw recommendations in your next security review. If your Dockerfiles haven't been touched since then, fixing them before the review forces you to is the cheaper path.
Related posts
When a Product Manager Ships Code, Who Owns the Outage?
In organizations where product managers write code with AI and several agents work at once, who checks what before a change reaches production? A role design for verification, approval, and recovery that is independent of the author, grounded in NIST SSDF, SLSA provenance, and the Google SRE postmortem culture.
John Baek
An Eclipse Plugin for the Agent Era — Task Context Was Already the Problem 20 Years Ago
The problem Mylyn set out to solve in the mid-2000s is the agent context problem. What changed is that the thing reading that context is no longer only a person.
Yeongsang Kim
A VS Code Extension for the Agent Era — What Developers Look At Now
Once writing code got cheap, a developer's time moved to judging and approving. Here is why those jobs cannot live outside the editor, and the choices behind the CollabOps VS Code extension.
Seungbaek Lee