this and that about programming

# Docker Anti-Patterns

Lukasz Kolko 2 min read
Table of Contents

Docker is easy to start with and easy to misuse. Below is a list of the most common Docker anti-patterns and how to fix them correctly.

1. Using the ‘latest’ Tag

Bad:

FROM node:latest

Problem:

  • Non-reproducible builds
  • Unexpected breakages after rebuilds

Good:

FROM node:25.5.0-alpine3.22

Always pin exact versions. Upgrade intentionally.

2. Bloated Base Images

Bad:

FROM ubuntu:22.04

Problem:

  • Unnecessary packages
  • Large image size
  • Bigger attack surface

Good:

FROM node:25-alpine3.22

Use the lightweight -alpine images whenever possible.

3. Killing the Build Cache

Bad:

COPY . .
RUN npm install

Problem:

  • Dependencies reinstalled on change any file

Good:

COPY package*.json ./
RUN npm ci
COPY . .

Order Dockerfile instructions by change frequency.

4. Running as Root

Bad:

WORKDIR /app
COPY . .
CMD npm run dev

Problem:

  • Compromised app can run with root privileges

Good:

ARG APP_USER_ID=10001
ARG APP_GROUP_ID=10001
WORKDIR /app
COPY --chown=${APP_USER_ID}:${APP_GROUP_ID} . .
USER ${APP_USER_ID}:${APP_GROUP_ID}
CMD npm run dev

Always drop root unless absolutely required.

5. Hardcoding Secrets

Bad:

ENV SECRET=...

Problem:

  • Secrets permanently stored in image layers

Good:

  • Pass secrets at runtime
  • Use Docker secrets

Example:

Terminal window
docker run -e SECRET=$SECRET myapp

Secrets must never end up in images.

6. Missing .dockerignore

Bad: No .dockerignore file

Problem:

  • Large build contexts
  • Slower builds

Good example:

.git
node_modules

Exclude everything not needed at runtime.

7. No Health Checks

Problem:

  • Running container does not mean healthy application

Good:

HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1

Health checks allow orchestrators to detect failures.

8. One Image for Development and Production

Bad:

  • Debug tools shipped to production

Problem:

  • Larger images
  • Increased attack surface

Good:

  • Use multi-stage builds
  • Separate development, build, and production stages

Example:

FROM node:25-alpine3.22 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# ...
FROM node:25-alpine3.22 AS development
WORKDIR /app
COPY --from=builder /app ./
# ... some dev tools ...
FROM node:25-alpine3.22 AS production
WORKDIR /app
COPY --from=builder /app ./
# ... some production tools ...

Build for environment (stage):

Terminal window
docker build -t myapp:dev --target development .

Build only the required target.

9. Inefficient Layer Usage

Bad:

RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y wget
RUN apt-get clean

Problem:

  • Inefficient because each RUN creates a new layer

Good:

RUN apt-get update && \
apt-get install -y curl && \
apt-get install -y wget && \
apt-get clean

Combine related commands into a single layer.

10. Never Updating Base Images

Problem:

  • Outdated images contain known CVEs

Good:

Terminal window
docker scout cves myapp:latest

Scan regularly and update base images on a schedule.