Overview
The Dockerfile defines how Docker builds an image. This cheat sheet covers all 17 primary instructions, advanced patterns like multi-stage builds and cache mounts, best practices for security and performance, and optimization techniques. All syntax is current for Docker 27.
Core instructions
FROM
| Instruction | Purpose | Example |
|---|---|---|
FROM <image>:<tag> |
Set the base image (must be the first line, except comments/ARG) | FROM ubuntu:24.04 |
FROM <image>:<tag> AS <stage> |
Set a named stage in a multi-stage build | FROM golang:1.22 AS builder |
FROM scratch |
Start from an empty base (for static binaries only) | FROM scratch |
Notes:
– You can use multiple FROM statements to create multiple build stages.
– Each FROM resets the filesystem; previous layers are discarded unless explicitly copied.
– scratch contains only what you explicitly add; smallest possible image.
RUN
| Instruction | Purpose | Example |
|---|---|---|
RUN <command> |
Execute a command during the build (shell form) | RUN apt-get update && apt-get install -y curl |
RUN ["executable", "param1", "param2"] |
Execute a command in exec form (no shell) | RUN ["python3", "-m", "pip", "install", "django"] |
Best practices:
– Use exec form when possible to avoid shell interpretation issues.
– Chain commands with && to keep layers small: RUN apt-get update && apt-get install && apt-get clean
– Clean package manager caches: rm -rf /var/lib/apt/lists/* (apt), apk del .build-deps (apk)
– Use --no-cache flags in package managers to reduce layer size.
COPY
| Instruction | Purpose | Example |
|---|---|---|
COPY <source> <dest> |
Copy files/directories from build context to image | COPY app.py /app/app.py |
COPY --chown=<user>:<group> <source> <dest> |
Copy and set ownership | COPY --chown=appuser:appuser . /app/ |
COPY --chmod=<mode> <source> <dest> |
Copy and set file permissions | COPY --chmod=755 script.sh /app/script.sh |
Best practices:
– COPY is preferred over ADD for simple file copying.
– Use .dockerignore to exclude build artifacts, tests, .git.
– Copy only what you need; split into multiple COPY lines for cache efficiency.
ADD
| Instruction | Purpose | Example |
|---|---|---|
ADD <source> <dest> |
Copy files/directories, with extra features (tar extraction, URL fetching) | ADD archive.tar.gz /app/ |
ADD --chown=<user>:<group> <source> <dest> |
Add with ownership | ADD --chown=appuser:appuser file.txt /app/ |
ADD --chmod=<mode> <source> <dest> |
Add with file permissions | ADD --chmod=644 config.txt /etc/config.txt |
Notes:
– If source is a tar/zip/gzip archive, it is extracted automatically.
– If source is a URL, it is downloaded (no extraction).
– COPY is usually clearer; use ADD only for tar extraction or URL fetching.
WORKDIR
| Instruction | Purpose | Example |
|---|---|---|
WORKDIR <path> |
Set the working directory for all subsequent commands (RUN, CMD, ENTRYPOINT, COPY, ADD) | WORKDIR /app |
Notes:
– If the directory doesn’t exist, it is created.
– Use absolute paths to avoid confusion.
– Multiple WORKDIR instructions are cumulative: WORKDIR /app then WORKDIR src results in /app/src.
ENV
| Instruction | Purpose | Example |
|---|---|---|
ENV <key>=<value> |
Set an environment variable (available in the running container) | ENV NODE_ENV=production |
ENV <key> <value> |
Alternative syntax (deprecated, avoid) | ENV DEBUG true |
Notes:
– Persists into containers; set once during build, available at runtime.
– Use for application-level configuration (database URLs, API keys for testing).
– Do not use ENV for secrets; use Docker secrets or pass at runtime.
ARG
| Instruction | Purpose | Example |
|---|---|---|
ARG <name>=<default> |
Define a build-time variable (not available in running container) | ARG PYTHON_VERSION=3.12 |
ARG <name> |
Define a variable with no default (must be provided with --build-arg) |
ARG BUILD_NUMBER |
Usage in build:
docker build --build-arg PYTHON_VERSION=3.11 -t myapp:1.0 .
Notes:
– ARG variables are used during build only; they do not appear in the final image.
– To pass an ARG value to the running container, re-assign it with ENV: ENV PYTHON_VERSION=$PYTHON_VERSION
EXPOSE
| Instruction | Purpose | Example |
|---|---|---|
EXPOSE <port> |
Document that a container listens on a port (does not publish it) | EXPOSE 8080 |
EXPOSE <port>/udp |
Document a UDP port | EXPOSE 53/udp |
EXPOSE <port1> <port2> |
Document multiple ports | EXPOSE 8080 8443 |
Notes:
– EXPOSE is metadata only; it does not actually publish the port.
– To publish, use docker run -p <host>:<container> or Docker Compose ports:.
– Useful for documentation and for docker run -P (random high-numbered ports).
USER
| Instruction | Purpose | Example |
|---|---|---|
USER <user> |
Set the user (or UID) for subsequent RUN, CMD, ENTRYPOINT, COPY, ADD | USER appuser |
USER <uid>:<gid> |
Use UID and GID directly | USER 1000:1000 |
Best practice for security:
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
USER appuser
Notes:
– Running as root in a container is a security risk.
– Use a dedicated non-root user to limit blast radius on container escape.
– Must be created before the USER instruction (use RUN).
HEALTHCHECK
| Instruction | Purpose | Example |
|---|---|---|
HEALTHCHECK --interval=<seconds> --timeout=<seconds> --start-period=<seconds> --retries=<count> CMD <command> |
Define a health check command | HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 CMD curl -f http://localhost:8000/health || exit 1 |
HEALTHCHECK NONE |
Disable a health check inherited from the base image | HEALTHCHECK NONE |
Options:
– --interval: How often to run the check (default: 30s)
– --timeout: How long to wait for the check (default: 30s)
– --start-period: Grace period before first check (default: 0s)
– --retries: Consecutive failures before marking unhealthy (default: 3)
Notes:
– Exit code 0 = healthy, 1 = unhealthy, 2 = reserved.
– Docker Compose and orchestrators use health status for restarting/replacing containers.
ENTRYPOINT
| Instruction | Purpose | Example |
|---|---|---|
ENTRYPOINT ["executable", "param1"] |
Define the container’s main process (exec form, preferred) | ENTRYPOINT ["python3", "-u", "app.py"] |
ENTRYPOINT command param1 |
Define the main process (shell form, avoid) | ENTRYPOINT python3 -u app.py |
Exec form vs. shell form:
– Exec form: Runs the executable directly; signals (SIGTERM) are delivered correctly; no shell interpretation.
– Shell form: Wraps command in /bin/sh -c; shell processes signals, not your app; can cause zombie processes.
With CMD:
ENTRYPOINT ["python3"]
CMD ["app.py"]
# docker run image arg → python3 app.py arg
Notes:
– ENTRYPOINT defines what cannot be easily overridden.
– Always use exec form for ENTRYPOINT.
CMD
| Instruction | Purpose | Example |
|---|---|---|
CMD ["executable", "param1", "param2"] |
Provide default arguments or command (exec form, preferred) | CMD ["python3", "app.py"] |
CMD command param1 |
Provide default arguments (shell form, avoid) | CMD python3 app.py |
CMD ["param1", "param2"] |
Provide default args to ENTRYPOINT | CMD ["production"] |
Notes:
– If no ENTRYPOINT is set, CMD is the command.
– If ENTRYPOINT is set, CMD provides arguments to it.
– Always use exec form for CMD.
VOLUME
| Instruction | Purpose | Example |
|---|---|---|
VOLUME <path> |
Mark a directory as a volume mount point | VOLUME /data |
VOLUME ["<path1>", "<path2>"] |
Mark multiple mount points | VOLUME ["/var/log", "/data"] |
Notes:
– VOLUME is declarative; it documents that a directory should be persisted or mounted.
– Does not actually create a volume; the volume is created at runtime.
– At runtime: docker run -v myvolume:/data image or docker run -v /host/path:/data image
LABEL
| Instruction | Purpose | Example |
|---|---|---|
LABEL <key>=<value> |
Add metadata to an image | LABEL maintainer="team@example.com" |
LABEL <key1>=<value1> <key2>=<value2> |
Add multiple labels | LABEL version="1.0" description="My app" |
Notes:
– Labels are searchable: docker images --filter "label=version=1.0"
– Common labels: maintainer, version, description, org.opencontainers.image.*
STOPSIGNAL
| Instruction | Purpose | Example |
|---|---|---|
STOPSIGNAL SIGTERM |
Signal to send when stopping the container (default: SIGTERM) | STOPSIGNAL SIGTERM |
STOPSIGNAL SIGKILL |
Force kill signal (rarely needed) | STOPSIGNAL SIGKILL |
Notes:
– Most apps handle SIGTERM (15) for graceful shutdown.
– Use SIGKILL (9) only if your app doesn’t handle SIGTERM.
SHELL
| Instruction | Purpose | Example |
|---|---|---|
SHELL ["<executable>", "<param>"] |
Override the default shell (default: ["/bin/sh", "-c"] on Linux) |
SHELL ["/bin/bash", "-c"] |
Use case:
FROM mcr.microsoft.com/windows/servercore:ltsc2022
SHELL ["powershell", "-Command", "-ErrorActionPreference", "Stop"]
RUN Get-ChildItem
ONBUILD
| Instruction | Purpose | Example |
|---|---|---|
ONBUILD <instruction> |
Trigger an instruction in child images (rarely used) | ONBUILD RUN npm install |
Notes:
– When a child image uses FROM <parent>, the ONBUILD instructions from the parent are executed.
– Useful for base images that orchestrate builds; avoid in application Dockerfiles.
Multi-stage builds
Multi-stage builds reduce final image size by using intermediate stages for building and a final stage with only runtime dependencies.
Pattern: Builder + Runtime
# Stage 1: Build
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o app .
# Stage 2: Runtime (minimal)
FROM alpine:3.20
COPY --from=builder /src/app /app
ENTRYPOINT ["/app"]
Key benefits:
– Build dependencies (compilers, dev tools) are not in the final image.
– Final image contains only the compiled binary.
– Can reduce image size from 500MB to 10MB or less.
Advanced: Named stages, conditional building
FROM golang:1.22 AS builder
ARG BUILD_TARGET=linux
WORKDIR /src
COPY . .
RUN GOOS=linux GOARCH=amd64 go build -o app .
FROM alpine:3.20 AS runtime
COPY --from=builder /src/app /app
USER nobody
ENTRYPOINT ["/app"]
# Use: docker build --target runtime -t myapp:latest .
Cache mounts (Docker 19.03+)
Cache mounts preserve files between builds, enabling faster package installations and dependency caching.
FROM python:3.12-alpine
WORKDIR /app
# Mount cache for pip packages
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
# Use: docker build --build-context . -t myapp:1.0 .
Use cases:
– Package manager caches (pip, npm, cargo, etc.)
– Build artifact caches (Go cache, Rust target, Maven .m2)
Build command:
docker build -t myapp:1.0 --build-context .
.dockerignore
Use .dockerignore to exclude files from the build context, reducing layer size and build time.
# .dockerignore
.git
.gitignore
.env
.env.local
*.pyc
__pycache__
.pytest_cache
node_modules
dist
build
*.log
README.md
.vscode
.idea
.DS_Store
Notes:
– Patterns work like .gitignore.
– Reduces context size sent to Docker daemon.
– Can significantly speed up builds on large projects.
Best practices for security
Run as non-root
FROM alpine:3.20
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --chown=appuser:appgroup . /app
USER appuser
ENTRYPOINT ["./app"]
Drop unnecessary capabilities
RUN setcap -r /bin/app # Remove all capabilities from executable
# Or at runtime: docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE image
Use read-only filesystem
# At runtime: docker run --read-only --tmpfs /tmp image
Minimal base images
alpine:3.20(~7 MB) — Recommended for most use casesdistroless/python3-nonroot(~50 MB) — Pre-hardened, no package managerscratch(~0 MB) — For static binaries only
Best practices for size
Clean package manager caches
# Alpine
RUN apk add --no-cache python3 py3-pip
# Debian/Ubuntu
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# pip
RUN pip install --no-cache-dir django
Combine RUN commands
# Bad (3 layers)
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# Good (1 layer)
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
Use COPY strategically
# Copy only what you need, in order of change frequency
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY src ./src
# Changing src doesn't invalidate the pip layer
Patterns you’ll reuse
# Base image with ARG for version
FROM python:${PYTHON_VERSION:-3.12}-alpine AS base
# Multi-stage with build args
FROM node:20-alpine AS builder
ARG NODE_ENV=production
RUN npm ci --only=production
# Health check with curl
HEALTHCHECK --interval=30s --timeout=5s \
CMD curl -f http://localhost:3000/health || exit 1
# Non-root user with static UID for reproducibility
RUN adduser -D -u 1000 appuser
# Copy with ownership for non-root
COPY --chown=appuser:appuser . /app
# Use build cache for package managers
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
# Exec form ENTRYPOINT + CMD
ENTRYPOINT ["python3", "-u"]
CMD ["app.py"]
FAQ
What’s the difference between COPY and ADD?
COPY is simpler and preferred for local files. ADD can extract tar archives and fetch URLs, but these features can cause unexpected behavior. Use COPY unless you specifically need tar extraction or URL fetching.
Should I use ENV or ARG?
ARG is for build-time variables (available only during build, not in the running container). ENV is for runtime variables (available in the running container). Use ARG for build configuration and ENV for application configuration.
What’s the difference between ENTRYPOINT and CMD?
ENTRYPOINT defines the container’s main process and usually cannot be overridden. CMD provides default arguments to ENTRYPOINT, or is the command if no ENTRYPOINT is set. Use exec form for both.
How do I write a Dockerfile that builds a small image?
Use a base image like alpine:latest or scratch. Use multi-stage builds to copy only compiled artifacts. Remove build dependencies. Clean package manager caches (rm -rf /var/lib/apt/lists/*). Avoid including unnecessary files (use .dockerignore).
Why should I run as a non-root user?
Running as root in a container means a breach or escape could give an attacker root access to the host. Create a dedicated user and drop capabilities to reduce risk. Add USER <uid> before the final command.
How this guide was verified
Written by hand from the official documentation, every command re-checked, and reviewed a second time before publishing. Last reviewed on .
Official sources read for this article

