Dockerfile cheatsheet.

Minimal

FROM python:3.13-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]

Instructions

FROM debian:12-slim                    # base image (alpine, distroless, ubi, etc)
LABEL maintainer="[email protected]"
LABEL org.opencontainers.image.source="https://github.com/me/repo"

ARG VERSION=1.0                        # build-time
ENV APP_VERSION=$VERSION               # runtime

WORKDIR /app                           # cd
COPY src/ ./src/
COPY --chown=app:app file .            # set ownership
ADD https://example.com/file.tar .     # auto-extract + URL fetch (prefer COPY)

RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

USER 1000:1000                         # drop root
EXPOSE 8080                            # documentation only
VOLUME /data                           # mount point

CMD ["python", "app.py"]               # default cmd (exec form)
ENTRYPOINT ["python", "app.py"]        # always-run prefix
HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost/ || exit 1

CMD vs ENTRYPOINT

# CMD: default, overridable by `docker run image other-cmd`
CMD ["python", "app.py"]

# ENTRYPOINT: always run, args appended
ENTRYPOINT ["python"]
CMD ["app.py"]
# `docker run image other.py` → runs `python other.py`

For scripts as entrypoint:

ENTRYPOINT ["/entrypoint.sh"]
CMD ["server"]

entrypoint.sh receives server as $1.

Multi-stage build

# Stage 1: build
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: prod
FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER 1000:1000
CMD ["node", "dist/server.js"]

Final image only has runtime files. Smaller, fewer attack surface.

Layer caching

Order from least → most often changed:

FROM python:3.13-slim
WORKDIR /app

# Cached unless requirements change
COPY requirements.txt .
RUN pip install -r requirements.txt

# Frequently changes; recopy doesn't invalidate above
COPY . .

If you COPY . . before pip install, every code change re-runs pip.

.dockerignore

.git
.venv
node_modules
__pycache__
*.pyc
.env.local
.next
dist
coverage

Reduces build context size + avoids leaking secrets.

BuildKit (modern)

# Enable (default in Docker 23+)
DOCKER_BUILDKIT=1 docker build .

Or use docker buildx build.

# syntax=docker/dockerfile:1.7
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

--mount=type=cache persists pip cache between builds.

secrets at build time

RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm install
docker build --secret id=npmrc,src=$HOME/.npmrc .

Secrets don’t end up in the image.

ssh forwarding

RUN --mount=type=ssh git clone [email protected]:me/private.git
docker build --ssh default .

ARG scope

ARG VERSION
FROM python:3.13-slim AS base
ARG VERSION                  # must re-declare after FROM
ENV APP_VERSION=$VERSION

ARG before FROM is only available to FROM. Re-declare after.

Conditional builds

ARG ENVIRONMENT=production

RUN if [ "$ENVIRONMENT" = "development" ]; then \
        pip install -r dev-requirements.txt; \
    fi

Or use stages:

FROM base AS dev
RUN pip install -r dev-requirements.txt

FROM base AS prod
# nothing extra
docker build --target dev .

Reproducible builds

# Pin everything
FROM python:3.13.0-slim-bookworm
RUN pip install requests==2.31.0

For OS packages, pin or use apt-get install -y --no-install-recommends + lockfile.

Smaller images

  1. Use alpine / slim / distroless.
  2. Multi-stage.
  3. Clean up in same RUN: apt-get update && apt-get install ... && rm -rf /var/lib/apt/lists/*.
  4. --no-install-recommends for apt.
  5. Don’t chmod -R huge trees.

distroless

FROM gcr.io/distroless/python3-debian12
WORKDIR /app
COPY --from=builder /app /app
CMD ["app.py"]

No shell, no package manager. Highest security, smallest.

ENV vs ARG

  • ARG: build-time only. Not in final image (except as metadata).
  • ENV: build-time + runtime. Persists in final image.
ARG BUILD_VERSION              # not in image
ENV APP_VERSION=$BUILD_VERSION # in image

Permissions

RUN useradd -m -u 1000 app
USER app

# Or numerical
USER 1000:1000

# Files
COPY --chown=app:app . .

Always drop root in final stage.

Health check tips

HEALTHCHECK --start-period=30s \
            --interval=30s \
            --timeout=3s \
            --retries=3 \
    CMD curl -fsS http://localhost:8000/health/ || exit 1

--start-period gives slow apps time to come up.

Common mistakes

  • COPY . . before deps install → cache invalidated by code changes.
  • No .dockerignore → huge build context.
  • apt-get update + install in separate RUNs → cache desync.
  • Running as root in final image.
  • CMD bash -c "..." (shell form) → signals not forwarded.
  • Missing --no-cache-dir for pip in prod → bloat.

Read this next

If you want my Dockerfile templates (Python, Node, Go), they’re at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .