
Most Docker problems don’t start at runtime.
They start in the Dockerfile.
A weak Dockerfile leads to:
- huge images
- slow CI pipelines
- security vulnerabilities
- containers that behave differently in production
A good Dockerfile makes Docker boring — predictable, fast, and secure.
This post walks from basic → intermediate → expert Dockerfile practices, using real examples and reasoning, not rules without context.
Part 1: Dockerfile Basics (Write What Docker Expects)
1. Start With a Clear, Minimal Base Image
Bad:
FROM ubuntu:latest
Better:
FROM python:3.11-slim
Why this matters:
- smaller base images reduce size and attack surface
latestis unpredictable and can break builds
Docker recommends choosing trusted, minimal base images and pinning versions
2. Use WORKDIR (Don’t Rely on cd)
Bad:
RUN cd /app && python app.py
Good:
WORKDIR /app
COPY . .
Each Dockerfile instruction runs in its own layer.WORKDIR makes intent explicit and reliable.
3. COPY Is Better Than ADD (Most of the Time)
Use:
COPY requirements.txt .
Avoid ADD unless you need:
- auto‑extract archives
- remote URLs
Keeping instructions explicit makes images easier to reason about.
Part 2: Intermediate Practices (Speed and Size Matter)
4. Order Instructions for Build Cache Efficiency
Docker caches layers.
If a layer changes, all layers after it rebuild.
Bad order:
COPY . .
RUN pip install -r requirements.txt
Good order:
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
This simple change can cut CI build time drastically by reusing cached dependency layers.
5. Use .dockerignore (Often Forgotten, Very Important)
Without .dockerignore, Docker sends everything in your directory as build context.
Create .dockerignore:
.git
node_modules
__pycache__
.env
tests/
This reduces:
- build time
- image size
- accidental secret leaks
6. Prefer Slim or Alpine — But Know the Trade‑Off
Options:
*-slim→ safe default for production*-alpine→ very small, but uses musl libcdistroless→ minimal runtime, no shell
Docker docs recommend using different images for build vs runtime when needed.
Part 3: Expert Practices (Production‑Grade Dockerfiles)
7. Multi‑Stage Builds (The Biggest Upgrade)
Multi‑stage builds separate:
- build tools
- runtime environment
Example (Node.js):
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build
# Runtime stage
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/index.js"]
Benefits:
- smaller final image
- no build tools in production
- faster pulls and startups
Docker explicitly recommends multi‑stage builds for production images.
8. Don’t Run Containers as Root
By default, containers run as root.
Create a non‑root user:
RUN adduser -D appuser
USER appuser
Why this matters:
- limits blast radius if container is compromised
- required by platforms like OpenShift
Security best‑practice sources consistently highlight this as critical.
9. Never Store Secrets in Dockerfiles
Bad:
ENV DB_PASSWORD=secret123
Why this is dangerous:
- Docker layers are inspectable
- secrets become part of image history
Correct approach:
- pass secrets at runtime
- use secret managers
Docker security guidance strongly warns against baking secrets into images.
10. Clean Package Manager Caches
Bad:
RUN apt-get update && apt-get install -y curl
Good:
RUN apt-get update && apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
This prevents unnecessary files from being permanently stored in image layers, reducing size and exposure.
A Production‑Ready Example Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN adduser --disabled-password appuser
USER appuser
CMD ["python", "main.py"]
This Dockerfile is: ✅ small
✅ cache‑friendly
✅ non‑root
✅ predictable
How to Think About Dockerfiles (Mental Model)
Think of a Dockerfile as:
a contract between build time and runtime
Anything not required at runtime should not exist in the final image.
If you design with that mindset:
- images get smaller
- security improves naturally
- builds become faster
InfraDecode Takeaway
A Dockerfile is not just a build script.
It’s an operational design decision.
Teams that master Dockerfiles rarely fight Docker in production.
— InfraDecode
Discover more from
Subscribe to get the latest posts sent to your email.
