Skip to content

Dockerfile 编写指南 ​

1. 最小示例 ​

dockerfile
# syntax=docker/dockerfile:1
FROM alpine:3.23

RUN addgroup -S app && adduser -S -G app -u 10001 app
WORKDIR /app

COPY --chown=app:app ./bin/server /app/server

USER app
EXPOSE 8080
ENTRYPOINT ["/app/server"]
CMD ["--listen", "0.0.0.0:8080"]

构建与运行:

bash
docker build --pull -t team-server:dev .
docker run --rm -p 127.0.0.1:8080:8080 team-server:dev

2. 构建上下文 ​

命令末尾的 . 是构建上下文:

bash
docker build -f docker/Dockerfile -t app:dev .

COPY 只能读取上下文中的文件,不能通过 ../ 逃出上下文。上下文过大会增加传输、缓存计算和 secret 泄露风险。

.dockerignore 示例:

text
.git
.env
*.pem
*.key
node_modules
dist
coverage
tmp
docs
!package-lock.json

用 docker build --progress=plain 观察上下文传输。不要把 .git、凭据或本地构建产物默认送入 builder。

3. FROM ​

dockerfile
FROM golang:1.26-alpine AS build
FROM gcr.io/distroless/static-debian13:nonroot

选择基础镜像:

  • 来自可信 Registry 和维护者;
  • 版本在支持期;
  • 大小适合,但不能只追求最小而牺牲可运维性;
  • 明确 libc、证书、时区和调试需求;
  • 生产可按 digest 锁定。
dockerfile
FROM alpine:3.23@sha256:REPLACE_WITH_APPROVED_DIGEST

按 digest 可复现,但不会自动获取安全更新;需要依赖更新机器人和定期重建。

4. RUN ​

Debian/Ubuntu:

dockerfile
RUN apt-get update \
    && apt-get install -y --no-install-recommends ca-certificates curl \
    && rm -rf /var/lib/apt/lists/*

Alpine:

dockerfile
RUN apk add --no-cache ca-certificates tzdata

将更新、安装和缓存清理放在同一层,避免复用过期 package index。安装包应最少且必要。

BuildKit cache mount:

dockerfile
RUN --mount=type=cache,target=/root/.cache/go-build \
    --mount=type=cache,target=/go/pkg/mod \
    go build -o /out/server ./cmd/server

5. COPY 与 ADD ​

dockerfile
COPY go.mod go.sum ./
COPY --chown=10001:10001 ./config /app/config

优先 COPY。ADD 具有 URL、Git 和本地 tar 自动解包等额外语义,只在明确需要时使用。远程下载通常用受校验的 RUN curl ... && sha256sum -c,或使用 BuildKit 支持的 checksum 能力。

5.1 缓存友好的顺序 ​

dockerfile
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN go build -o /out/server ./cmd/server

先复制低频变化的依赖清单,再复制源码,可复用依赖层。

6. WORKDIR ​

dockerfile
WORKDIR /app

优于:

dockerfile
RUN cd /app && ...

WORKDIR 会影响后续 RUN、CMD、ENTRYPOINT、COPY 和 ADD,必要时会创建目录。

7. ARG 与 ENV ​

dockerfile
ARG VERSION=dev
ENV APP_VERSION=$VERSION
  • ARG 主要在构建时使用,但值可能出现在构建历史或证明材料中。
  • ENV 保留在镜像配置和运行容器中。
  • 二者都不是 Secret 机制。

Secret:

dockerfile
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm ci

构建:

bash
docker build --secret id=npmrc,src="$HOME/.npmrc" .

8. CMD 与 ENTRYPOINT ​

推荐 exec form:

dockerfile
ENTRYPOINT ["/app/server"]
CMD ["--config", "/app/config.yaml"]

运行时:

bash
# 替换 CMD,保留 ENTRYPOINT
docker run app:latest --config /tmp/test.yaml

# 替换 ENTRYPOINT
docker run --entrypoint sh app:debug

Shell form:

dockerfile
CMD /app/server --config /app/config.yaml

会经 /bin/sh -c,环境变量展开方便,但信号和参数行为不同。服务进程优先 exec form。

9. USER ​

dockerfile
RUN addgroup --system --gid 10001 app \
    && adduser --system --uid 10001 --ingroup app app
USER 10001:10001

最后的运行阶段应以非 root 用户运行。需要绑定 80/443 时,通常让反向代理发布宿主端口到容器高端口,而不是给应用 root。

构建时 USER 会影响后续 RUN,必要时:

dockerfile
USER root
RUN ...
USER 10001:10001

10. EXPOSE ​

dockerfile
EXPOSE 8080/tcp

这是文档性元数据,不会开放宿主端口,也不能替代应用监听地址。

11. VOLUME ​

dockerfile
VOLUME ["/var/lib/app"]

镜像作者声明 VOLUME 后,使用者会得到隐式数据生命周期,可能产生匿名卷。团队内部镜像通常更适合在 Compose/部署层明确挂载,让运维知道卷名称、备份和保留策略。

12. HEALTHCHECK ​

dockerfile
HEALTHCHECK --interval=30s --timeout=3s \
  --start-period=20s --retries=3 \
  CMD ["/app/healthcheck"]

或禁用继承检查:

dockerfile
HEALTHCHECK NONE

健康检查二进制必须存在。不要为了 curl 健康端点给极简镜像增加大量无关包,可内置小型检查命令。

13. LABEL ​

dockerfile
LABEL org.opencontainers.image.title="team-api" \
      org.opencontainers.image.version="$VERSION" \
      org.opencontainers.image.revision="$REVISION" \
      org.opencontainers.image.source="https://git.example.internal/team/api"

构建元数据应与 CI 中实际 Git commit 对应。敏感仓库 URL 或内部结构按组织策略处理。

14. 多阶段构建 ​

Go 示例:

dockerfile
# syntax=docker/dockerfile:1
FROM golang:1.26-alpine AS build
WORKDIR /src

COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download

COPY . .
ARG TARGETOS
ARG TARGETARCH
RUN --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
    go build -trimpath -ldflags="-s -w" -o /out/server ./cmd/server

FROM gcr.io/distroless/static-debian13:nonroot
COPY --from=build --chown=nonroot:nonroot /out/server /server
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/server"]

构建依赖不进入最终镜像,可显著减少攻击面和大小。

15. Node.js 多阶段示例 ​

dockerfile
# syntax=docker/dockerfile:1
FROM node:24-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci

FROM deps AS build
COPY . .
RUN npm run build

FROM node:24-alpine AS runtime
ENV NODE_ENV=production
WORKDIR /app
RUN addgroup -S app && adduser -S -G app -u 10001 app
COPY --from=deps --chown=app:app /app/node_modules ./node_modules
COPY --from=build --chown=app:app /app/dist ./dist
COPY --chown=app:app package.json ./
USER app
CMD ["node", "dist/server.js"]

实际框架可能需要 production dependency pruning、standalone 输出或运行时静态资源,应依据项目构建方式调整。

16. Shell 安全 ​

管道中前面的命令失败,默认 /bin/sh 可能仍返回最后一个命令的状态。支持的镜像可配置:

dockerfile
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN curl -fsSL https://example.invalid/archive.tar.gz \
    | tar -xz -C /opt

更稳妥的是下载、校验、再解包。Alpine 的 /bin/sh 不等于 bash,不要盲目设置不存在的 shell。

17. 缓存失效 ​

Dockerfile 指令与依赖内容共同决定缓存。常用操作:

bash
docker build --no-cache --pull -t app:fresh .
docker build --progress=plain -t app:debug .

--no-cache 不等于重新拉取基础镜像,所以安全重建通常还要 --pull。

18. 可复现性 ​

提高可复现性:

  • 锁定基础镜像 digest;
  • 锁定语言依赖;
  • 固定构建器版本/兼容版本;
  • 不在构建中依赖当前时间;
  • 使用 SOURCE_DATE_EPOCH(工具支持时);
  • 记录 source revision、SBOM、provenance;
  • 在干净环境重复构建并比较 digest。

完全可复现需要工具链和压缩/时间戳等所有输入一致,不是只锁一个 base image。

19. Lint 与测试 ​

bash
docker build --check .
docker build --target test --progress=plain .

可定义测试阶段:

dockerfile
FROM build AS test
RUN go test ./...

CI 先构建 test target,再构建最终 target。扫描最终镜像和构建证明材料。

20. 反模式 ​

  • 使用浮动 latest 且不记录 digest。
  • 把 token、SSH key 或 .env COPY 进镜像。
  • 用 ARG 传长期 secret。
  • 最终阶段保留编译器、包管理缓存和测试数据。
  • 以 root 运行服务。
  • 用 chmod -R 777 解决权限。
  • 把数据库数据写进镜像或容器层。
  • 每次 COPY . . 都在依赖安装前。
  • 在容器启动脚本里在线安装依赖。
  • 用 docker commit 替代 Dockerfile。

21. Review 清单 ​

  • [ ] 首行声明受支持的 Dockerfile syntax
  • [ ] base image 来源可信、版本受支持、更新策略明确
  • [ ] .dockerignore 排除敏感和无关文件
  • [ ] 依赖锁定且缓存顺序合理
  • [ ] 使用多阶段构建
  • [ ] 最终镜像无编译工具和包缓存
  • [ ] 不含凭据
  • [ ] 使用非 root 用户
  • [ ] exec-form 启动并正确处理信号
  • [ ] 健康检查低开销、无副作用
  • [ ] OCI labels、SBOM、provenance 可追溯
  • [ ] 镜像扫描和真实启动测试通过

22. 官方参考 ​

继续阅读