Dockerfile Best Practices Cheatsheet
This cheatsheet covers the most commonly used Dockerfile instructions and best practices, including base image selection, build instructions, file operations, startup configuration, multi-stage builds, and optimization tips.
Base Image (FROM)
Choosing the right base image is the first step in writing a Dockerfile.
``dockerfile
# Use a specific version (recommended)
FROM node:20-alpine
# Name stages for multi-stage builds FROM golang:1.22 AS builder
# Use slim base images FROM python:3.12-slim
# Use distroless images (runtime only)
FROM gcr.io/distroless/base-debian12
`
Build Instructions (RUN)
Execute commands during image build.
`dockerfile
# Merge multiple commands to reduce layers
RUN apt-get update && apt-get install -y \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Use exec form (recommended) RUN ["npm", "install"]
# Cache mount for faster builds RUN --mount=type=cache,target=/root/.npm \ npm install
# Use HERE document
RUN <`
File Operations (COPY / ADD)
Copy files from the build context into the image.
`dockerfile
# Copy specific files (recommended)
COPY package.json package-lock.json ./
# Copy entire directory COPY src/ /app/src/
# Set ownership COPY --chown=node:node config/ /app/config/
# Copy from previous stage (multi-stage) COPY --from=builder /app/dist /usr/share/nginx/html
# ADD with auto-extraction
ADD app.tar.gz /opt/app/
`
Working Directory & Environment (WORKDIR / ENV / ARG)
`dockerfile
# Set working directory (use absolute paths)
WORKDIR /app
# Set environment variables (available at runtime) ENV NODE_ENV=production \ PORT=3000
# Build arguments (overridable at build time) ARG APP_VERSION=1.0.0 ARG BUILD_DATE
# Pass build arg to env var
ENV APP_VERSION=$APP_VERSION
`
Expose Ports & Volumes (EXPOSE / VOLUME)
`dockerfile
# Declare container listening ports
EXPOSE 8080
EXPOSE 443/tcp 80/udp
# Declare mount points (anonymous volumes)
VOLUME ["/data", "/var/log"]
`
Startup Configuration (CMD / ENTRYPOINT)
`dockerfile
# CMD as default command (overridable)
CMD ["node", "server.js"]
CMD npm start
# ENTRYPOINT as fixed entry point ENTRYPOINT ["docker-entrypoint.sh"] ENTRYPOINT ["nginx", "-g", "daemon off;"]
# Combined: ENTRYPOINT + CMD ENTRYPOINT ["python"] CMD ["app.py"]
# SHELL vs exec form
# exec form (recommended): CMD ["executable", "param1"]
# shell form: CMD command param1
`
User & Labels (USER / LABEL)
`dockerfile
# Create user and switch (security best practice)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# Set image metadata
LABEL maintainer="team@example.com"
LABEL version="1.0.0"
LABEL description="Production web application"
`
Health Check (HEALTHCHECK)
`dockerfile
# Container health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# Disable inherited health check
HEALTHCHECK NONE
`
Triggers & Shell (ONBUILD / SHELL)
`dockerfile
# Trigger: executes when this image is used as a base
ONBUILD COPY . /app/src
ONBUILD RUN make build
# Custom SHELL (Windows scenario)
SHELL ["powershell", "-Command"]
SHELL ["/bin/bash", "-c"]
`
Multi-stage Builds
Separate build environment from runtime environment to significantly reduce image size.
`dockerfile
# Stage 1: Build
FROM golang:1.22 AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o app .
# Stage 2: Runtime FROM alpine:3.20 RUN apk add --no-cache ca-certificates tzdata COPY --from=builder /build/app /app EXPOSE 8080 USER 1001 ENTRYPOINT ["/app"]
# Named stages for conditional builds FROM node:20-alpine AS base WORKDIR /app COPY package*.json ./
FROM base AS dev RUN npm install --only=development COPY . .
FROM base AS prod
RUN npm install --only=production
COPY . .
`
Best Practices
`dockerfile
# 1. Use .dockerignore to exclude unnecessary files
# .dockerignore contents:
# node_modules
# .git
# *.md
# Dockerfile
# .env
# 2. Order instructions by change frequency (cache optimization) FROM node:20-alpine # Least changing: package files COPY package*.json ./ RUN npm install # Most changing: source code COPY . .
# 3. Use specific tags, not latest # Wrong: FROM node:latest # Right: FROM node:20.11-alpine3.19
# 4. Minimize image layers # Merge multiple RUN commands RUN apt-get update && \ apt-get install -y curl git && \ apt-get clean && \ rm -rf /var/lib/apt/lists/*
# 5. Don't run as root RUN adduser -D myuser USER myuser
# 6. Use --no-install-recommends RUN apt-get install -y --no-install-recommends curl
# 7. Set timezone and locale ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime ``
Base Image(4)
| Command | Level | ||
|---|---|---|---|
FROM node:20-alpineSpecify base image with a specific version tag | Basic | FROM node:20-alpine | |
FROM golang:1.22 AS builderName a build stage for multi-stage builds | Intermediate | FROM golang:1.22 AS builder | |
FROM python:3.12-slimUse a slim base image to reduce size | Basic | FROM python:3.12-slim | |
FROM gcr.io/distroless/base-debian12Use distroless image containing only runtime | Expert | FROM gcr.io/distroless/base-debian12 |
Build(9)
| Command | Level | ||
|---|---|---|---|
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*Merge RUN commands, clean apt cache after installing packages | Basic | RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* | |
RUN ["npm", "install"]Use exec form to run commands (avoids shell escaping issues) | Intermediate | RUN ["npm", "install"] | |
RUN --mount=type=cache,target=/root/.npm npm installUse BuildKit cache mount to speed up builds | Expert | RUN --mount=type=cache,target=/root/.npm npm install | |
RUN <<EOF set -e\npip install --no-cache-dir -r requirements.txt\nEOFUse HERE document format for multi-line RUN commands | Intermediate | RUN <<EOF\nset -e\npip install --no-cache-dir -r requirements.txt\nEOF | |
WORKDIR /appSet working directory (use absolute paths) | Basic | WORKDIR /app | |
ENV NODE_ENV=production PORT=3000Set environment variables (available at runtime) | Basic | ENV NODE_ENV=production PORT=3000 | |
ARG APP_VERSION=1.0.0Declare build-arg, overridable with --build-arg at build time | Intermediate | ARG APP_VERSION=1.0.0 | |
LABEL version="1.0.0" description="Production web application"Add metadata labels to the image | Basic | LABEL version="1.0.0" description="Production web application" | |
SHELL ["/bin/bash", "-c"]Change the default shell | Expert | SHELL ["/bin/bash", "-c"] |
File Ops(4)
| Command | Level | ||
|---|---|---|---|
COPY package.json package-lock.json ./Copy specific files to leverage Docker layer caching | Basic | COPY package.json package-lock.json ./ | |
COPY src/ /app/src/Copy an entire directory into the image | Basic | COPY src/ /app/src/ | |
COPY --chown=node:node config/ /app/config/Copy files and set ownership | Intermediate | COPY --chown=node:node config/ /app/config/ | |
ADD app.tar.gz /opt/app/Add and auto-extract tar archive | Intermediate | ADD app.tar.gz /opt/app/ |
Multi-stage(2)
| Command | Level | ||
|---|---|---|---|
COPY --from=builder /app/dist /usr/share/nginx/htmlCopy artifacts from a previous build stage | Intermediate | COPY --from=builder /app/dist /usr/share/nginx/html | |
# 多阶段构建示例Multi-stage build: separate build and runtime environments | Expert | FROM golang:1.22 AS builder
WORKDIR /build
COPY . .
RUN go build -o app .
FROM alpine:3.20
COPY --from=builder /build/app /app
ENTRYPOINT ["/app"] |
Startup(6)
| Command | Level | ||
|---|---|---|---|
EXPOSE 8080Declare container listening port (documentation only) | Basic | EXPOSE 8080 | |
VOLUME ["/data"]Declare an anonymous mount volume | Intermediate | VOLUME ["/data"] | |
CMD ["node", "server.js"]Provide default command (overridable by docker run) | Basic | CMD ["node", "server.js"] | |
ENTRYPOINT ["nginx", "-g", "daemon off;"]Set container entry point (not overridable without --entrypoint) | Intermediate | ENTRYPOINT ["nginx", "-g", "daemon off;"] | |
ENTRYPOINT ["python"]\nCMD ["app.py"]Combine ENTRYPOINT and CMD | Intermediate | ENTRYPOINT ["python"]\nCMD ["app.py"] | |
HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD curl -f http://localhost:8080/health || exit 1Set container health check | Expert | HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD curl -f http://localhost:8080/health || exit 1 |
Best Practices(7)
| Command | Level | ||
|---|---|---|---|
RUN addgroup -S appgroup && adduser -S appuser -G appgroup\nUSER appuserCreate non-root user and switch (security best practice) | Intermediate | RUN addgroup -S appgroup && adduser -S appuser -G appgroup\nUSER appuser | |
ONBUILD COPY . /app/srcSet trigger instruction executed when this image is used as a base | Expert | ONBUILD COPY . /app/src | |
# .dockerignore 文件内容Use .dockerignore to exclude unnecessary files from build context | Basic | node_modules
.git
*.md
Dockerfile
.env | |
ENV TZ=Asia/ShanghaiSet container timezone | Basic | ENV TZ=Asia/Shanghai\nRUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime | |
RUN apt-get install -y --no-install-recommends curlInstall system packages without recommended dependencies | Intermediate | RUN apt-get install -y --no-install-recommends curl | |
# 按变更频率排序指令Place less-frequently-changing instructions first to maximize cache usage | Intermediate | FROM node:20-alpine
COPY package*.json ./
RUN npm install
COPY . . | |
FROM node:20.11-alpine3.19Use precise version tags instead of latest | Basic | FROM node:20.11-alpine3.19 |