Dockerfile Cheatsheet - Docker Image Build Reference

A Dockerfile authority for developers who want their images small, fast to finalize, and without leaked secrets. Beyond listing instructions, it explains why layer ordering and build cache matter, how multi-stage builds keep runtime images lean, and how ADD vs COPY, ENV vs ARG, USER and HEALTHCHECK change security and behavior. By the end you can write a Dockerfile that builds quickly and produces a minimal, production-safe image.

Containers & Orchestration·38 commands·Last updated 2026-07-21
dockerdockerfileContainersImages

Typical Use Case

For developers who write Dockerfiles and care about image size. There are only two techniques that matter much, and both are powerful: multi-stage builds (install dependencies and toolchain at build time, ship only artefacts at run time, which can take an image from gigabytes to megabytes) and layer caching (copy the dependency manifest first so code edits do not bust the install layer). This sheet is organised into seven groups with complete examples you can adapt directly.

Base Instructions 7

FROM node:18-alpine
Set the base image; alpine variants are smaller
FROM scratch
Empty base image for minimal static binaries
WORKDIR /app
Set working directory, created automatically if missing
COPY package*.json ./
Copy files into the container, supports globs
RUN npm ci
Execute a command to build an image layer
EXPOSE 3000
Document a container port (no actual mapping)
CMD ["node", "app.js"]
Default start command, exec form is recommended

Env & Args 7

ENV NODE_ENV=production
Set an env var, also available at runtime
ENV PATH="/app/bin:$PATH"
Append to PATH to make custom commands global
ARG VERSION=1.0
Build-time argument, not present at runtime
ARG NPM_TOKEN
No default; pass via --build-arg
LABEL maintainer="email@example.com"
Add image metadata
USER appuser
Switch the runtime user to lower container privileges
HEALTHCHECK --interval=30s CMD curl -f http://localhost/ || exit 1
Health check; marks unhealthy after consecutive failures

File Operations 5

COPY src/ ./src/
Copy a directory into the container, target auto-created
COPY --chown=node:node . .
Set file owner on copy to avoid root ownership
COPY --chmod=755 entrypoint.sh /entrypoint.sh
Set exec permission directly on copy (BuildKit)
ADD https://example.com/file.tar.gz /tmp/
ADD can download URLs into the image
ADD file.tar.gz /app/
Auto-extract local tar; prefer COPY for plain files

Multi-stage Build 5

FROM node:18 AS builder
Name a build stage for later reference
COPY --from=builder /app/dist ./dist
Copy artifacts from a build stage to the final image
COPY --from=nginx:alpine /etc/nginx/nginx.conf /etc/nginx/
Copy files from an external image without pulling first
FROM golang:1.21 AS builder
Compile stage of a Go multi-stage build
FROM alpine:3.18
Final image uses a smaller base, down to a few MB

Entrypoint 5

CMD ["node", "app.js"]
Default command, overridable by docker run args
ENTRYPOINT ["node"]
Fixed entrypoint, not overridden by run args
ENTRYPOINT ["node"] CMD ["app.js"]
Combine: ENTRYPOINT sets program, CMD sets default args
SHELL ["/bin/bash", "-c"]
Change default shell for RUN/CMD, common in Windows containers
ENTRYPOINT ["./docker-entrypoint.sh"]
Custom entry script for init before the main process

Build Optimization 6

COPY package*.json ./
Copy deps first then npm ci to leverage the cache layer
RUN npm ci && npm cache clean --force
Merge RUN to reduce layers and clean cache
RUN apk add --no-cache curl
Alpine installs without caching indexes, smaller image
.dockerignore
Exclude node_modules, .git and other unneeded build files
RUN --mount=type=cache,target=/root/.npm npm ci
BuildKit cache mount reuses npm packages across builds
FROM node:18-alpine
alpine base is ~5x smaller than the full image

Full Examples 3

FROM node:18 AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:18-alpine WORKDIR /app COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist CMD ["node", "dist/index.js"]
Node multi-stage: build deps and compile, runtime keeps only artifacts and deps
FROM python:3.11 AS builder WORKDIR /app COPY requirements.txt . RUN pip install --user -r requirements.txt FROM python:3.11-slim WORKDIR /app COPY --from=builder /root/.local /root/.local COPY . . CMD ["python", "main.py"]
Python multi-stage: deps installed to --user, runtime copies only that dir
FROM golang:1.21 AS builder WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -o /app/server . FROM alpine:3.18 COPY --from=builder /app/server /app/server CMD ["/app/server"]
Go multi-stage build: static binary keeps the image under 10 MB

Parameter matrix

ParameterEffectExample
FROM node:18-alpine指定基础镜像,alpine 体积约为完整版 1/5FROM node:18-alpine
WORKDIR /app设置工作目录,不存在会自动创建WORKDIR /app
CMD ["node","app.js"]默认启动命令,exec 形式可正确接收 SIGTERMCMD ["node", "app.js"]
ENTRYPOINT ["node"]固定入口点,不被 run 参数覆盖ENTRYPOINT ["./docker-entrypoint.sh"]
ENV设置环境变量,构建期与运行期都可用ENV NODE_ENV=production
ARG仅构建期可用的参数ARG VERSION=1.0
COPY --chown复制时指定属主,避免产物为 rootCOPY --chown=node:node . .
ADD vs COPYADD 可下载 URL 与自动解压,普通文件用 COPYADD file.tar.gz /app/
多阶段 AS builder命名构建阶段供后续引用FROM node:18 AS builder
COPY --from从构建阶段或外部镜像复制产物COPY --from=builder /app/dist ./dist
USER切换运行用户降低容器权限USER appuser
HEALTHCHECK健康检查,连续失败标记 unhealthyHEALTHCHECK --interval=30s CMD curl -f http://localhost/ || exit 1
.dockerignore排除 node_modules、.git 等不需要进镜像的文件.dockerignore
--mount=type=cacheBuildKit 缓存挂载,跨构建复用包缓存RUN --mount=type=cache,target=/root/.npm npm ci

Common pitfalls

Symptom代码改一行就要重装全部依赖,构建非常慢。

CauseCOPY . . 放在安装依赖之前,代码变更击穿了安装层的缓存。

Fix先 COPY package*.json ./ 再 RUN npm ci,装完再 COPY 剩余代码。

Symptom容器 stop 时要等十秒才退出。

Cause用了 shell 形式 CMD,shell 作为 PID 1 不转发信号给子进程。

Fix改用 exec 形式 CMD ["node", "app.js"],让应用直接作为 PID 1 接收 SIGTERM。

Symptomdocker run 传入的参数覆盖了镜像里指定的启动程序。

Cause用了 CMD 而非 ENTRYPOINT,run 后面的参数会整体替换 CMD。

FixENTRYPOINT 定住程序、CMD 只给默认参数:ENTRYPOINT ["node"] 加 CMD ["app.js"]。

Symptom镜像里能看到密码明文,删了也还在。

Cause--build-arg 传的密钥会留在构建历史中,即使后续层删掉也能翻出来。

Fix改用 BuildKit secrets(RUN --mount=type=secret);已推送的镜像需轮换密钥。

Symptom镜像体积高达数 GB,远超预期。

Cause单阶段构建把编译工具链和源码都留在最终镜像里。

Fix改多阶段构建:builder 阶段装依赖编译,最终阶段只 COPY 产物。

Symptom容器内文件属主是 root,应用进程读写自己的目录报权限错。

CauseCOPY 默认以 root 属主写入镜像。

FixCOPY --chown=node:node . .,并配合 USER 切换到非 root 用户运行。

Symptom把 node_modules 打进了镜像,本机平台的二进制在容器里跑不起来。

Cause构建上下文没排除 .dockerignore 里的 node_modules。

Fix在 .dockerignore 中排除 node_modules,让镜像内用 npm ci 安装对应平台的版本。

Symptom容器启动即退出,日志里看不出原因。

Cause启动命令路径错误或文件不可执行,Dockerfile 本身不报错。

Fix用 docker run --rm -it <image> sh 进入容器手动执行;检查 WORKDIR 与 CMD 路径是否匹配。

Troubleshooting

  1. 1进容器手动排查启动失败

    docker run --rm -it <image> sh

    镜像不可交互时可加 --entrypoint sh;手动跑一次启动命令能看到真实报错。

  2. 2优化构建缓存命中

    COPY package*.json ./

    依赖清单单独成层,装完依赖后再 COPY 代码,代码改动不再触发重装。

  3. 3多阶段构建只取产物

    COPY --from=builder /app/dist ./dist

    最终镜像不含源码与工具链,体积通常能降一个数量级。

  4. 4BuildKit 缓存挂载加速安装

    RUN --mount=type=cache,target=/root/.npm npm ci

    npm 包缓存跨构建复用,比每层重新下载快很多;需开启 BuildKit。

  5. 5确认最终镜像包含什么

    docker history <image>

    逐层看大小来源,能直接定位是哪个 RUN / COPY 让体积暴涨。

Command Examples

Node multi-stage build: full toolchain at build time, artifacts only at runtime

FROM node:18 AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:18-alpine WORKDIR /app COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist CMD ["node", "dist/index.js"]

依赖文件先 COPY 再 npm ci,改业务代码只让源码层失效、依赖层继续命中缓存。这一层顺序直接决定构建耗时。

Output

STEP 5/12 : RUN npm run build
 ---> Running in 8f3a1c
> app@1.0.0 build
> vite build
vite v5.0.0 building for production...
✓ 1423 modules transformed.
dist/index.html                   0.46 kB
Successfully built in 12.34s

Keep the build context small with .dockerignore

# .dockerignore node_modules .git *.log .env dist

没有 .dockerignore 时整个 node_modules 会被传进构建上下文,上下文动辄几百 MB,拖慢每次构建;顺带还有把 .env 打进镜像的风险。

Scope difference between ARG and ENV

ARG VERSION=1.0 # 仅构建时可用,镜像里不存在 ENV NODE_ENV=production # 构建时与运行时都可用 RUN echo $VERSION $NODE_ENV

ARG 定义的变量不会自动进入后续 RUN 的环境,需要 ENV 才能在镜像里保留。切换构建阶段版本号用 ARG,运行时需要读的配置用 ENV。

Output

1.0 production

Common Pitfalls

  • Secrets baked into ARG or ENV remain in the image layer permanently — deleting them in a later layer does not hide them from docker history.
  • Bad COPY ordering invalidates the dependency cache, turning a seconds-long build into minutes on every code change.
  • A single-stage build keeps the entire toolchain and source in the final image, easily adding hundreds of MB; use multi-stage and copy only artifacts.
  • Running as root means a container escape immediately yields root; create a non-root user and switch with a USER instruction.
  • CMD is replaced wholesale by arguments after docker run, which loses the program path; fix the program with ENTRYPOINT.

Tips

  • Each RUN/COPY/ADD instruction creates a new layer — merge RUN commands to reduce layer count.
  • Multi-stage builds greatly shrink the final image, ideal for compiled languages like Go, Rust, Java.
  • Use .dockerignore to exclude node_modules, .git, .env and other files not needed for the build.
  • CMD vs ENTRYPOINT: CMD can be overridden by docker run arguments; ENTRYPOINT is fixed.
  • Prefer the exec form ["cmd","arg"] over the shell form — it receives SIGTERM correctly and avoids stop timeouts.
  • Don't pass secrets via --build-arg; they persist in the image build history. Use BuildKit secrets instead.

FAQ

Why does the order of COPY instructions in a Dockerfile matter?

Every RUN, COPY or ADD produces a cached layer, and any change invalidates everything below it. Copying package*.json first and running npm ci afterwards keeps the dependency layer cached, so editing application code only invalidates the source layer. In the reverse order, every code change forces a full dependency reinstall and turns a few-second build into minutes.

What problem does multi-stage building solve?

It separates build-time from runtime dependencies: the builder stage installs the full toolchain and compiles, while the final stage FROMs a slim base and copies in artifacts with COPY --from=builder. Compilers, headers and dev dependencies never reach the final image. The effect is most dramatic for Node projects, where the runtime image ends up holding only dist and node_modules.

What is the difference between ARG and ENV?

ARG is a build-time parameter passed with --build-arg and it does not exist once the build finishes. ENV is both a build-time and a runtime environment variable and is baked into the image. Use ARG for one-off values like a stage version tag, and ENV for anything the running container must see, such as NODE_ENV. A common gotcha: an ARG value is not automatically available to later RUN steps unless you also declare it as ENV.

How should CMD and ENTRYPOINT be combined?

ENTRYPOINT fixes the executable and is not easily replaced by arguments after docker run, while CMD supplies the default arguments and is substituted by them. The usual pattern is ENTRYPOINT ["node"] with CMD ["app.js"], so the default run is node app.js while docker run image other.js becomes node other.js. If the whole command lives in CMD, the program itself is lost when it is overridden.

Should I use COPY or ADD?

Use COPY for ordinary file copying — its behavior is explicit. ADD additionally fetches a URL and auto-extracts local tarballs. Reserve it for those two cases, such as ADD package.tar.gz /app/ when you want extraction; otherwise prefer COPY to avoid surprises from implicit unpacking. Remember to pair the build with a .dockerignore excluding node_modules, .git and similar.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us