5 Dockerfile Misconceptions & How to Fix Them

Video by DreamsofCode · Watch on YouTube

Quick Summary

Most engineers build Docker images the same way they've always done it — cargo-culting patterns without understanding the tradeoffs. This video debunks five common Dockerfile misconceptions, from base image choices to multi-stage builds, and shows how fixing them saves time, disk space, and money.

The core message: challenge your defaults. Alpine isn't always better. COPY . . isn't the problem — missing .dockerignore is. Multi-stage builds can shrink Go images from 272 MB to 2.3 MB.

Table of Contents

1. Alpine vs Slim: The Base Image Trap 2. Layer Order & Cache Invalidation 3. The Build Context Bloat (.dockerignore) 4. Multi-Stage Builds: Slashing Image Size 5. Multi-Process Containers with Supervisord Bonus: Pin Digests, Not Tags

Misconception 1 — Alpine vs Slim: The Base Image Trap

MISCONCEPTION 1

The Problem

Alpine is tiny, but it uses musl libc — not the glibc that virtually all application releases target. What looks like a size win often becomes a nightmare:

The Fix

TagBaseSizeBest For
SlimDebian-based (glibc)~120 MBDefault — compatibility + small
Alpinemusl libc~30 MBOnly if you verify all deps work
FullFull OS packages~1 GBDevelopment / debugging
Prefer this
FROM python:3.12-slim

Avoid unless verified
FROM python:3.12-alpine

Misconception 2 — Layer Order & Cache Invalidation

MISCONCEPTION 2

The Problem

Docker caches layers. If an earlier layer's input changes, all subsequent layers rebuild. Common mistake: COPY . . before RUN npm install. Every code edit invalidates the entire dependency install.

BAD — every code change re-runs npm install
COPY . .
RUN npm ci

The Fix

Copy what changes slowly first, what changes fast last. Think of it like an onion — peel from the outside.

GOOD — dependency cache survives code edits
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

If only src/index.js changes, the npm ci layer is cached — instant rebuild.

Misconception 3 — The Build Context Bloat

MISCONCEPTION 3

The Problem

COPY . . isn't the bad pattern. The bad pattern is sending garbage into the build context. Without .dockerignore, Docker sends node_modules, .git, logs, build artifacts — making builds slow and cache keys huge.

Some teams respond with 20+ precise COPY commands — fragile and error-prone.

The Fix

Use .dockerignore the same way you use .gitignore:

node_modules/
.git/
*.log
dist/
.env

Then COPY . . is clean and readable. Rebuilding shows zero context transferred for ignored files.

Clean and fast
COPY . .  # Only non-ignored files are sent

Misconception 4 — Multi-Stage Builds

MISCONCEPTION 4

The Problem

Compiled languages like Go often get built in a single stage with a full base image — including the entire Go toolchain, compilers, and headers that the binary doesn't need at runtime.

BAD — 272 MB image
FROM golang:1.22-alpine
COPY . .
RUN go build -o /app .
CMD ["/app"]

The Fix

Builder stage for compilation, minimal runtime stage for execution:

GOOD — 2.3 MB image
FROM golang:1.22-alpine AS builder
COPY . .
RUN CGO_ENABLED=0 go build -o /app .

FROM scratch
COPY --from=builder /app /app
CMD ["/app"]

Size Comparison

Base ImageSize
golang:alpine272 MB
alpine + binary11 MB
scratch + binary2.3 MB

Scratch vs Distroless

ImageSizeShellTLS CertsUsers
scratch0 MBNoNoNo
distroless~2 MBNoYesYes

Tip: Use distroless for most cases — small but less annoying than scratch.

Misconception 5 — Multi-Process Containers

MISCONCEPTION 5

The Myth

"One process per container."

The Reality

It's a vibe, not a law. When your app needs nginx + a Python server and you're not at Kubernetes scale, splitting into two containers adds complexity that outweighs the benefit.

The Fix

Use supervisord — a lightweight process control system:

FROM python:3.12-slim
RUN apt-get update && apt-get install -y nginx supervisor
COPY app.py /app/
COPY nginx.conf /etc/nginx/
COPY supervisord.conf /etc/supervisor/

CMD ["supervisord", "-c", "/etc/supervisor/supervisord.conf"]
; supervisord.conf
[program:nginx]
command=nginx -g "daemon off;"
autostart=true

[program:app]
command=python /app/app.py
autostart=true

Multiple processes in one container is fine when they're tightly coupled and the alternative (separate containers) adds more operational debt.

Bonus — Pin Digests, Not Tags

BONUS

Tags like node:22-slim or latest can be moved — accidentally or intentionally. Pin by digest for reproducible builds:

Fragile — tag can move
FROM node:22-slim

Immutable — pin by digest
FROM node@sha256:abc123...

Use docker buildx imagetools inspect <image>:<tag> to find the digest.

Tool: D-Roast

D-Roast is a Rust utility that scans your Dockerfile and roasts bad patterns — npm installnpm ci, COPY . . without .dockerignore, missing digest pins. Run it locally or in CI.


Key Takeaways

MisconceptionBad PatternGood Pattern
Alpine is always betterFROM ...:alpinePrefer slim (glibc) unless verified
Cache doesn't matterCOPY . . before depsCopy lockfile first, then npm ci, then code
COPY . . is badOver-engineered multi-copy.dockerignore + single COPY . .
Single-stage buildsOne image for everythingBuilder + minimal runtime stage
One process per containerComplex multi-containerSupervisord for tightly-coupled processes
Tags are permanentFROM node:latestPin by digest hash

Guide generated from Dockerfile Misconceptions by DreamsofCode