Dev Tools

5 Dockerfile Mistakes That Are Quietly Bloating Your Images in 2026

The Dockerfile mistakes that pass code review every time — unpinned base images, root containers, and layer bloat — and how to catch them before they ship.

📅 Aug 17, 2026·⏱️ 5 min read·✍️ Cikal Studio Labs
🐳

Why Dockerfiles Rot Quietly

A Dockerfile is one of the few files in a repo that almost never gets a proper code review. It works, the build passes, and nobody looks at it again until the image is 1.2GB and someone asks why staging deploys take four minutes. Most of the damage comes from a handful of repeat offenders that are easy to miss by eye but trivial to catch with a line-by-line static check.

1. Unpinned Base Images

FROM node:latest or even bare FROM node both resolve to whatever "latest" happens to mean on build day. That's not a version — it's a moving target. A build that passed yesterday can silently break today because the upstream maintainer pushed a new major version to the same tag. Pin to an explicit version, and where reproducibility really matters, pin to a digest: FROM node:20.11-slim@sha256:....

2. Running as Root by Default

If a Dockerfile never sets a USER instruction, the container runs as root — not because anyone chose that, but because it's the default nobody overrode. This matters most in the failure case: if an attacker gets code execution inside the container (a vulnerable dependency, a deserialization bug, whatever), root privileges make lateral movement and privilege escalation dramatically easier, especially if the Docker socket or host paths are anywhere near the mount list. Adding a dedicated non-root user is a few lines and costs nothing at runtime.

3. One RUN Per Command

Every RUN instruction creates a new image layer. Five separate RUN apt-get install lines create five layers, each carrying its own diff, even though logically they're one step. Chaining related commands with && — ideally with line continuations for readability — collapses them into a single layer and usually shrinks the image measurably.

4. Copying the Whole Build Context

COPY . . is the path of least resistance and also the easiest way to accidentally ship .git, node_modules, .env, or your local IDE config into a production image. A .dockerignore file — or scoping the COPY to specific paths — fixes this in one line and also improves Docker's layer-cache hit rate, since unrelated file changes stop invalidating the copy layer.

5. Leaving the Package Cache Behind

apt-get install without --no-install-recommends pulls in optional packages you probably don't need, and without a following rm -rf /var/lib/apt/lists/* in the same RUN instruction, the downloaded package index sits in the layer forever — cleaning it up in a later RUN doesn't help, since the earlier layer still contains it.

Catching These Automatically

None of these checks require a container runtime or a real build — they're pattern checks against the raw Dockerfile text. That's exactly what the Dockerfile Best Practices Linter does: paste the file, get line-numbered findings with plain-English explanations, and fix what matters before it reaches CI.

  1. Paste your Dockerfile.
  2. Review flagged lines — issues vs. cautions are separated.
  3. Fix and re-check until it's clean.

It runs entirely in your browser, so nothing you paste ever leaves your machine.

Frequently Asked Questions

Does this tool actually build or run the Dockerfile?

No — it performs static, line-by-line text analysis of what you paste. It doesn't require Docker installed and never executes anything, which is also why it works fully offline.

Will it catch every possible Dockerfile issue?

It focuses on the highest-impact, most common issues: unpinned base images, missing non-root USER, excessive RUN layers, broad COPY patterns, and unclean package caches. It's a fast pre-check, not a replacement for a full security scanner.

Is there a tool to check a Dockerfile for best practices online?

Yes — the Dockerfile Best Practices Linter does exactly this, entirely in your browser with no upload step. It's a one-time $5.99 purchase — no subscription, no account required.

Does it flag every RUN instruction as a problem?

No — a small number of RUN instructions is normal and not flagged. It only raises a caution when it detects a long run of separate RUN lines (4 or more in a row) that would likely benefit from being combined.

Can I use this on Dockerfiles with multi-stage builds?

Yes — the linter tracks each FROM as a new build stage and resets its USER/RUN tracking accordingly, so multi-stage Dockerfiles are analyzed correctly rather than treated as one flat file.