DK

Common Mistakes

Docker · 8 entries

Running Containers as Root

syntax
# Problem:
FROM node:20
CMD ["node", "server.js"]
# Runs as root by default
example
# Fix:
FROM node:20-alpine
WORKDIR /app
COPY --chown=node:node . .
RUN npm ci --omit=dev
USER node
CMD ["node", "server.js"]

Note Most base images default to root. This means if an attacker exploits your application, they have root inside the container — and potentially on the host via privilege escalation. Always add a USER instruction before CMD.

Bloated Image Size

syntax
# Problem:
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3 build-essential ...
COPY . .
# 1.5GB image for a simple Python app
example
# Fix:
FROM python:3.12-slim AS build
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

FROM python:3.12-slim
COPY --from=build /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY . /app
CMD ["python", "/app/main.py"]

Note Common culprits: using the full Ubuntu/Debian base, leaving build tools installed, not cleaning apt/pip caches, copying node_modules into the image. Use docker history to identify the largest layers.

Not Pinning Image Versions

syntax
# Problem:
FROM node:latest
FROM python
FROM nginx
example
# Fix — pin to specific versions:
FROM node:20.11.1-alpine3.19
FROM python:3.12.2-slim-bookworm
FROM nginx:1.25.4-alpine

Note An image tag like :latest or :20 can change at any time. Your build might work today and fail tomorrow because the base image was updated. In CI, a non-reproducible build means you cannot investigate a production issue with the exact same image.

Dangling Images & Orphan Volumes

syntax
# Check dangling images:
docker images -f dangling=true
# Check orphan volumes:
docker volume ls -f dangling=true
example
# Frequent rebuilds leave untagged images:
docker images -f dangling=true
# REPOSITORY  TAG     SIZE
# <none>      <none>  450MB
# <none>      <none>  450MB

# Clean them:
docker image prune
docker volume prune

Note Every time you rebuild with the same tag, the old image loses its tag and becomes <none>:<none>. These pile up fast. Volumes from removed containers also linger. Schedule regular prune commands or add them to your CI cleanup steps.

Port Conflicts on the Host

syntax
# Error: bind: address already in use
example
# Problem:
docker run -d -p 3000:3000 --name api-v1 myapp:1.0
docker run -d -p 3000:3000 --name api-v2 myapp:2.0
# Error: port is already allocated

# Fix — use different host ports:
docker run -d -p 3001:3000 --name api-v2 myapp:2.0
output
Error response from daemon: driver failed: Bind for 0.0.0.0:3000 failed: port is already allocated

Note Two containers cannot bind the same host port. Check what is using a port with lsof -i :3000 (macOS/Linux) or docker ps --format to see existing mappings. Remember that host services (like a locally running Node process) also count.

Accidental Build Cache Invalidation

syntax
# Problem — COPY . . before RUN npm ci
COPY . .
RUN npm ci
example
# Every source code change re-runs npm ci (~30-60 seconds).
# Fix — copy manifests first:
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

Note Docker invalidates a layer's cache when any input changes. A broad COPY . . changes whenever ANY file in your project changes, nuking the cache for all subsequent layers. Order Dockerfile instructions from least-frequently to most-frequently changing.

Using ADD When You Mean COPY

syntax
# ADD has hidden behaviors:
ADD app.tar.gz /app/    # auto-extracts
ADD https://x.com/f /f  # downloads
example
# Use COPY for plain file copies (clear intent):
COPY package.json ./
COPY src/ ./src/

# Only use ADD when you specifically want extraction:
ADD assets.tar.gz /app/assets/

Note ADD auto-extracts tar archives and can fetch URLs, which is rarely what you want. COPY does exactly one thing — copies files. Using COPY makes your Dockerfile easier to reason about and avoids accidental extraction of files you wanted to keep as archives.

Baking Secrets into Images

syntax
# NEVER do this:
ENV DATABASE_URL=postgres://user:pass@host/db
COPY .env /app/.env
example
# Bad — secret is in the image layer history:
ENV API_KEY=sk-abc123...

# Fix — pass secrets at runtime:
docker run -e API_KEY=sk-abc123 myapp:1.0
docker run --env-file .env myapp:1.0

# For build-time secrets (e.g., private npm token):
docker build --secret id=npmrc,src=.npmrc .
# In Dockerfile:
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci

Note ENV values and COPYed files are permanently visible via docker history and docker inspect. Even if you delete them in a later layer, they exist in the previous layer. Use --secret mounts for build-time secrets and runtime env vars or secret managers for runtime secrets.