mcpField
D
docker-build-strategies

docker-build-strategies

github.com/docker云与 DevOps6021.3k 次安装5 天前更新
代码仓库

关于 docker-build-strategies

用于编写和审查 Dockerfile 的 Skill,涵盖多阶段构建、缓存、安全与镜像体积,并提供语言专项参考、示例文件和构建验证脚本。

主要功能

  1. 01多阶段构建模式
  2. 02BuildKit 层与缓存指导
  3. 03密钥和 SSH 挂载实践
  4. 04.dockerignore 与非 root 运行指导
  5. 05镜像体积及构建验证

适用场景

  1. 01为应用创建 Dockerfile
  2. 02审查或优化现有镜像构建
  3. 03添加 .dockerignore 并保护构建凭据
  4. 04检查镜像体积和运行用户配置

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/docker/skills --skill docker-build-strategies

安装后在 Claude Code 中输入/docker-build-strategies确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

skill.yaml
agents/
openai.yaml
assets/
Dockerfile.go
Dockerfile.nodejs
Dockerfile.python
dockerignore-example
checks/
verification.md
references/
layer-caching.md
multi-stage-builds.md
scripts/
verify-build.sh

详细介绍

Docker 构建策略

概述

本技能提供编写和审查生产级 Dockerfile 的规则和模式。适用于主要关注镜像构建质量的任务:多阶段构建、缓存行为、以非 root 用户运行、构建上下文清理以及运行时镜像大小。

何时使用此技能

在以下情况启用此技能:

  • 为任何语言或框架创建新的 Dockerfile
  • 优化现有 Dockerfile 的大小、速度或安全性
  • 审查 Dockerfile 是否符合最佳实践
  • 为项目添加 .dockerignore 文件

何时不使用此技能

在以下情况不要使用此技能:

  • 项目尚无 Docker 配置,主要需求是搭建初始脚手架
  • 主要任务是在 compose.yaml 中连接各项服务
  • 主要任务是调试 Compose 启动顺序、网络或开发环境覆盖配置

核心指南

多阶段构建

当项目包含构建步骤,或构建时依赖与运行时依赖不同时,使用多阶段构建。将构建时依赖与运行时镜像分开。

  1. 为每个阶段显式命名(FROM ... AS build、FROM ... AS runtime)。
  2. 为运行时阶段选择合适的最小基础镜像:distroless、alpine 或 slim 变体。
  3. 使用 COPY --from=build 将最终构建产物复制到运行时阶段。
  4. 从前一阶段复制内容或添加静态文件时使用 COPY --link,以便让 COPY 不依赖之前的层,从而提高缓存复用率。

有关特定语言的模式(Go、Node、Python、Java),请参阅 references/multi-stage-builds.md。

层缓存

按照从最不常变化到最常变化的顺序排列 Dockerfile 指令。

  1. 在复制应用源代码之前,先放置依赖清单(package.json、go.mod、requirements.txt)并执行安装步骤。使用绑定挂载将清单挂载到安装步骤中,而不是通过 COPY 复制,这样清单就不会进入镜像层:RUN --mount=type=bind,source=package.json,target=package.json --mount=type=bind,source=package-lock.json,target=package-lock.json npm ci。对于只读取清单的安装命令(npm ci、pip install -r、go mod download),这样做是安全的;如果某个步骤还需要将清单写回镜像,则改用 COPY。
  2. 使用 BuildKit 缓存挂载来缓存包管理器的缓存:
    • Go:RUN --mount=type=cache,target=/go/pkg/mod go build ...
    • Node:RUN --mount=type=cache,target=/root/.npm npm ci
    • Python:RUN --mount=type=cache,target=/root/.cache/pip pip install ...
    • apt:RUN --mount=type=cache,target=/var/cache/apt,sharing=locked --mount=type=cache,target=/var/lib/apt,sharing=locked apt-get update && apt-get install -y ... — 无需执行 rm -rf /var/lib/apt/lists/*,因为缓存位于镜像层之外。apt 需要独占访问其缓存目录,因此必须使用 sharing=locked。
    • apk(Alpine — 根据 Alpine wiki,此文档未经 Docker 验证;references/layer-caching.md 提供了源链接):RUN --mount=type=cache,target=/etc/apk/cache,sharing=locked apk add ... — 去掉 --no-cache,这样下载的软件包会存入挂载的缓存目录,而不是被丢弃。
  3. 将基础镜像标签固定到特定版本或摘要 — 生产环境中绝不要使用 latest。
  4. 使用 && 合并相关的 RUN 命令,以减少层数;但为保留缓存粒度,应将逻辑上不同的步骤分开。

有关缓存失效规则和缓存挂载模式的详细信息,请参阅 references/layer-caching.md。

构建密钥与 SSH 访问

切勿将凭据烘焙进镜像。使用 BuildKit 密钥和 SSH 挂载,使凭据仅在需要它们的特定 RUN 步骤期间可用,并且绝不持久保存在任何层或 docker history 输出中。

  1. 切勿通过 ARG 或 ENV 传递凭据。它们最终都会进入镜像层,并且可通过 docker history 检查。
  2. 切勿将凭据文件 COPY 到构建上下文中:.npmrc、.pypirc、.netrc、pip.conf、Maven settings.xml、.env、云凭据(~/.aws/credentials、~/.config/gcloud/、服务账号 JSON 文件、~/.azure/)、密钥管理器令牌(~/.vault-token)、包注册表令牌(~/.cargo/credentials.toml)、TLS 密钥(*.pem、*.p12)、kubeconfig、SSH 密钥(id_rsa、id_dsa、id_ed25519、id_ecdsa)。即使最终阶段没有将它们复制过去,它们仍会留在中间层和构建缓存中。
  3. 切勿在 RUN 命令中回显、写入或展开密钥值,以免将其持久保存到某个层中,或输出到构建日志。访问密钥文件(例如 /run/secrets/<id>,或直接通过挂载的 target= 访问)——绝不要执行 echo "$(cat /run/secrets/X)",也绝不要将其替换到会通过 --progress=plain 记录的 shell 参数中。
  4. 对包管理器注册表凭据,使用 RUN --mount=type=secret:
    RUN --mount=type=secret,id=npmrc,target=/root/.npmrc,required=false \
        --mount=type=cache,target=/root/.npm \
        npm ci --omit=dev
    
    密钥仅在该 RUN 中可用,绝不会写入某个层。若构建始终需要凭据(例如所有包都来自私有注册表,因此缺少密钥应立即导致构建失败),请使用 required=true;仅当密钥为可选项(仅使用公共包也能成功构建)时,才使用 required=false。
  5. 获取私有 Git 仓库或模块时,使用 RUN --mount=type=ssh。构建容器默认没有 known_hosts——请在同一个 RUN 中填充该文件:
    RUN --mount=type=ssh \
        mkdir -p -m 0700 /root/.ssh && \
        ssh-keyscan github.com >> /root/.ssh/known_hosts && \
        git clone [email protected]:org/private-repo.git
    
    切勿为了省事使用 StrictHostKeyChecking=no——它会完全禁用主机密钥验证。每次步骤运行时,ssh-keyscan 都会接受服务器提供的任何主机密钥;不同构建之间不会固定密钥。若要获得更强保障,请将其与服务提供商公布的主机密钥指纹进行比对,或将公布的密钥写入 known_hosts,而不是进行扫描。
  6. 使用密钥和 SSH 源调用 buildx:
    # --ssh default forwards this shell's SSH agent (SSH_AUTH_SOCK); list every key the build can use:
    ssh-add -l
    
    docker buildx build \
        --secret id=npmrc,src=$HOME/.npmrc \
        --ssh default \
        .
    
    RUN --mount=type=ssh 步骤可以使用 ssh-add -l 列出的每个密钥,因此只公开此构建所需的密钥。在交互式终端中,运行 ssh-agent bash,启动一个使用专用 Agent 的 shell,然后运行 ssh-add <key-file>,确认 ssh-add -l 只列出该密钥,并在此 shell 中运行构建。每条命令都会启动新 shell 的工具无法在命令之间保留该 Agent,因此请让用户运行这些步骤。或者,使用 --ssh default=<key-file> 直接传入未加密的密钥文件,例如专用部署密钥;BuildKit 会拒绝以这种方式传入受密码短语保护的密钥,因此应改为将此类密钥加载到 Agent 中。
  7. .dockerignore 中对 .env 和凭据文件的排除属于纵深防御,而非主要防护机制——请保留这些排除项,但不要将其作为唯一的保护措施。

请参阅 references/multi-stage-builds.md,了解各语言的模式(npm、pip、Maven、Go GOPRIVATE)。

.dockerignore

始终在 Dockerfile 旁生成一个 .dockerignore。排除:

  • .git/、.github/、.vscode/、.idea/
  • node_modules/、__pycache__/、.venv/、vendor/(在构建阶段重新构建时)
  • *.md、LICENSE、docs/
  • 构建输出、测试产物和 IDE 配置
  • .env 文件及任何密钥

请参阅 assets/dockerignore-example,获取完整模板。

非 root 用户

始终将最终镜像配置为以非 root 用户运行。

  1. 在运行阶段创建专用用户和组:
    RUN addgroup --system --gid 1001 appgroup && \
        adduser --system --uid 1001 --ingroup appgroup appuser
    
  2. 设置应用文件的所有权:COPY --from=build --chown=appuser:appgroup /app /app
  3. 将 --chown 与 COPY --link 结合使用时,始终使用分配给用户和组的数字 UID:GID(例如,如果上文使用了 --uid 1001 --gid 1001,则使用 --chown=1001:1001),不要使用命名用户。--link 会创建独立层,其中无法使用之前 RUN 指令创建的命名用户。
  4. 将 USER appuser 指令放在所有文件操作之后、ENTRYPOINT/CMD 之前。
  5. 对于 distroless 镜像,使用内置的非 root 用户:USER nonroot:nonroot。
镜像体积优化
  1. 运行阶段优先使用 FROM scratch(Go 静态二进制文件)、distroless 或基于 Alpine 的镜像。
  2. 使用 BuildKit 缓存挂载安装 OS 软件包,而不要在同一层中通过 rm -rf 删除缓存;请参阅上文的“层缓存”。缓存挂载会使软件包缓存完全不进入镜像层,因此无需清理。
  3. 不要在运行时镜像中安装文档、man 手册页或调试工具。
  4. 积极使用 .dockerignore,尽可能缩小构建上下文。
通用规则
  • 始终将 # syntax=docker/dockerfile:1 指令作为第一行,以启用 BuildKit 功能。
  • 在任何 COPY 或 RUN 指令之前设置 WORKDIR,绝不要依赖默认的 /。
  • 优先使用 exec 格式的 ENTRYPOINT(["binary"]),而不是 shell 格式。
  • 添加 EXPOSE 以说明监听端口。
  • 添加元数据标签:LABEL org.opencontainers.image.source=...

相关技能

  • 首次搭建 Docker 项目框架并确定要创建哪些文件时,请使用 docker-project-foundations。
  • 涉及服务依赖、健康检查、覆盖配置、网络和卷模式时,请使用 docker-compose-patterns。
  • 涉及具有破坏性的 Docker CLI 命令(docker system prune、docker rm -f、镜像/网络/构建器清理),以及破坏性命令防护措施的跨产品索引时,请使用 docker-destructive-guardrails。

参考资料

  • references/multi-stage-builds.md — Go、Node.js、Python 和 Java 的语言专属多阶段构建模式
  • references/layer-caching.md — 深入介绍层排序、缓存失效和 BuildKit 缓存挂载

资源文件

  • assets/Dockerfile.go — 使用 distroless 运行时和非 root 用户的 Go 多阶段构建
  • assets/Dockerfile.nodejs — 使用适当层缓存和非 root 用户的 Node.js 多阶段构建
  • assets/Dockerfile.python — 使用虚拟环境、层排序和非 root 用户的 Python 构建
  • assets/dockerignore-example — 完整的 .dockerignore 模板

脚本

  • scripts/verify-build.sh — 构建当前目录中的 Dockerfile,然后报告镜像大小和已配置的用户。从项目根目录(包含 Dockerfile 的目录)运行,并使用此技能目录下的脚本路径:
    bash "<skill-dir>/scripts/verify-build.sh" [--help] [IMAGE_NAME]
    
    将 <skill-dir> 替换为包含此 SKILL.md 的文件夹的绝对路径;scripts/ 路径相对于该文件夹,而非项目目录。不要先切换到技能目录:脚本会构建当前目录中的内容。如果无法解析技能目录,请运行 docker build -t verify-build-test .,然后运行 docker images verify-build-test 和 docker inspect verify-build-test --format '{{.Config.User}}'。所有 Docker 命令均成功或请求了帮助时,退出状态为 0;验证失败时,退出状态为失败的 Docker 命令所返回的非零状态;参数无效时,退出状态为 2。

检查

  • checks/verification.md — 供人工审查使用的详细验证操作手册。

评论

支持 Markdown