Dockerfile 编写速查表 - Docker 镜像构建指令大全

面向需要把应用打包成镜像的开发者。Dockerfile 的价值不在一句句清单,而在层顺序、构建缓存与多阶段构建这些组合规则——顺序不当会每次全量重编、体积暴涨,不加 USER 会让容器以 root 运行增加被攻面。读完能写出一份构建快速、运行镜像精简,且不把密钥和源码误打进镜像的 Dockerfile,并理解 ADD/COPY、ENV/ARG 为何要按场景区分。

容器与编排·共 38 条命令·最后更新 2026-07-21
dockerdockerfile容器镜像

典型使用场景

面向需要写 Dockerfile 与优化镜像体积的开发者。核心技巧只有两条但威力最大:多阶段构建(编译期装依赖与工具链,运行期只带产物,可把镜像从 GB 级压到几 MB)与分层缓存(先 COPY 依赖清单再装依赖,代码改动就不会击穿安装层)。本表按基础指令、环境变量与参数、文件操作、多阶段构建、入口点、构建优化、完整示例七组组织,每个示例都可直接对照修改。

基础指令 7

FROM node:18-alpine
指定基础镜像,alpine 版本体积更小
FROM scratch
空基础镜像,用于极致最小化静态二进制
WORKDIR /app
设置工作目录,不存在会自动创建
COPY package*.json ./
复制文件到容器,支持通配符
RUN npm ci
执行命令构建镜像层
EXPOSE 3000
声明容器端口,仅文档作用不实际映射
CMD ["node", "app.js"]
容器默认启动命令,exec 形式推荐

环境变量与参数 7

ENV NODE_ENV=production
设置环境变量,运行时也可用
ENV PATH="/app/bin:$PATH"
追加 PATH 让自定义命令全局可用
ARG VERSION=1.0
构建时参数,运行时不存在
ARG NPM_TOKEN
无默认值,从 --build-arg 传入
LABEL maintainer="email@example.com"
添加镜像元数据
USER appuser
切换运行用户降低容器权限
HEALTHCHECK --interval=30s CMD curl -f http://localhost/ || exit 1
健康检查,连续失败到阈值标记为 unhealthy

文件操作 5

COPY src/ ./src/
复制目录到容器,目标不存在会自动创建
COPY --chown=node:node . .
复制时指定文件属主,避免 root 拥有
COPY --chmod=755 entrypoint.sh /entrypoint.sh
复制时直接设置可执行权限(BuildKit)
ADD https://example.com/file.tar.gz /tmp/
ADD 可下载 URL 文件到镜像
ADD file.tar.gz /app/
自动解压本地 tar 包,普通文件用 COPY 更明确

多阶段构建 5

FROM node:18 AS builder
命名构建阶段,便于后续引用
COPY --from=builder /app/dist ./dist
从构建阶段复制产物到最终镜像
COPY --from=nginx:alpine /etc/nginx/nginx.conf /etc/nginx/
从外部镜像复制文件,不必先 pull
FROM golang:1.21 AS builder
Go 多阶段构建的编译阶段
FROM alpine:3.18
最终镜像用更小基础,体积可降至几 MB

入口点 ENTRYPOINT 5

CMD ["node", "app.js"]
默认命令,可被 docker run 参数覆盖
ENTRYPOINT ["node"]
固定入口点,不会被 run 参数覆盖
ENTRYPOINT ["node"] CMD ["app.js"]
组合使用:ENTRYPOINT 定程序 CMD 定默认参数
SHELL ["/bin/bash", "-c"]
改变 RUN/CMD 的默认 shell,Windows 容器常用
ENTRYPOINT ["./docker-entrypoint.sh"]
自定义入口脚本做初始化后再启动主进程

构建优化 6

COPY package*.json ./
先复制依赖文件再 npm ci,利用缓存层
RUN npm ci && npm cache clean --force
合并 RUN 减少层数并清理缓存
RUN apk add --no-cache curl
Alpine 安装包不缓存索引,减小体积
.dockerignore
排除 node_modules、.git 等不需要构建的文件
RUN --mount=type=cache,target=/root/.npm npm ci
BuildKit 挂载缓存,跨构建复用 npm 包提速
FROM node:18-alpine
alpine 基础镜像比完整版小约 5 倍

完整示例 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 多阶段构建:编译期装依赖编译,运行期只带产物和依赖
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 多阶段构建:依赖装到 --user 目录,运行镜像只复制该目录
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

参数矩阵

参数作用示例
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

易错点与避坑指南

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

原因COPY 默认以 root 属主写入镜像。

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

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

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

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

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

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

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

排障路径

  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 让体积暴涨。

命令示例

Node 多阶段构建:编译期装全依赖,运行期只带产物

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,改业务代码只让源码层失效、依赖层继续命中缓存。这一层顺序直接决定构建耗时。

输出

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

用 .dockerignore 排除无关内容

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

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

ARG 与 ENV 的作用域差别

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

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

输出

1.0 production

常见坑与注意事项

  • 把 .env 或密钥写进 ARG/ENV 会永久留在镜像层里,即使后续层删掉也能被 docker history 翻出。
  • COPY 顺序不当会让依赖层缓存失效,每次改代码都重装依赖,构建从秒级变分钟级。
  • 单阶段构建把编译工具链和源码都留在最终镜像里,体积可能多出数百 MB;用多阶段构建只 COPY 产物。
  • 以 root 运行容器时一旦被突破即获得容器内 root 权限;应创建非 root 用户并在 USER 指令切换。
  • CMD 会被 docker run 后的参数整体替换,命令写 CMD 里会让程序路径丢失;固定程序用 ENTRYPOINT。

提示

  • 每条 RUN/COPY/ADD 指令都会创建新层,尽量合并 RUN 命令减少层数。
  • 多阶段构建可以大幅减小最终镜像体积,适合编译型语言如 Go、Rust、Java。
  • 使用 .dockerignore 排除 node_modules、.git、.env 等不需要构建的文件。
  • CMD 和 ENTRYPOINT 区别:CMD 可被 docker run 参数覆盖,ENTRYPOINT 是固定的。
  • 优先用 exec 形式 ["cmd","arg"] 而非 shell 形式,前者能正确接收 SIGTERM 信号,避免容器停止超时。
  • 别用 --build-arg 传密码或密钥,它会留在镜像构建历史里,用 BuildKit secrets 方案更安全。

常见问题

为什么 Dockerfile 里要调整 COPY 的顺序?

因为每个 RUN/COPY/ADD 都会生成一层缓存,上层变了下面全部作废。要先 COPY package*.json ./ 再 RUN npm ci,这样改业务代码只会让源码层失效,依赖层继续命中缓存。顺序反过来的话每改一次代码就要重装全部依赖,构建从几秒变成几分钟。

多阶段构建解决了什么问题?

把构建期依赖和运行期依赖分开:builder 阶段装完整工具链完成编译,最终阶段只 FROM 一个精简基础镜像并 COPY --from=builder 拷产物。这样编译期的编译器、头文件、开发依赖都不会进入最终镜像。对 Node 项目效果最明显,运行镜像可以只剩 dist 与 node_modules。

ARG 和 ENV 有什么区别?

ARG 是构建时参数,可用 --build-arg 传入,但构建结束后就消失;ENV 既是构建时也是运行时环境变量,最终镜像里能读到。切换构建阶段版本号这种一次性的用 ARG,需要容器运行时可访问的(如 NODE_ENV)用 ENV。一个常见坑是 ARG 定义的变量不会自动进入后续 RUN 的环境,需要配 ENV 才能在镜像里保留。

CMD 和 ENTRYPOINT 怎么配合?

ENTRYPOINT 固定执行哪个程序,不容易被 docker run 的参数覆盖;CMD 提供默认参数,被 run 后面的参数替换。常见写法是 ENTRYPOINT ["node"] 加 CMD ["app.js"],于是默认执行 node app.js,而 docker run image other.js 会变成 node other.js。如果把命令写进 CMD,则整个会被替换,程序路径就丢了。

COPY 和 ADD 该用哪个?

普通文件复制一律用 COPY,它行为明确只做复制。ADD 多了两个能力:可以从 URL 下载文件,以及自动解压本地 tar 包。除了确实需要这两项(比如 ADD package.tar.gz /app/ 顺便解压),否则优先用 COPY,避免隐式解压带来的意外。另外要记得配 .dockerignore 排除 node_modules、.git 等不需要进镜像的内容。

官方参考来源

下方为命令对应的官方权威文档,供你核对最新用法与深入查阅。

由 巧匠 维护

公开更新于 2026年7月21日,内容持续校对官方文档。

联系我们

命令或描述有误?提交反馈、商务合作或产品建议都可发送邮件给我们。

联系我们