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
- Use
alpine/slim/distroless. - Multi-stage.
- Clean up in same RUN:
apt-get update && apt-get install ... && rm -rf /var/lib/apt/lists/*. --no-install-recommendsfor apt.- Don’t
chmod -Rhuge 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-dirfor 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 .