mcpField
D
docker-pro

docker-pro

github.com/bretfisher云与 DevOps2141 次安装2 周前更新
代码仓库

关于 docker-pro

docker-pro 是面向创建、审查和现代化 Docker 构建与运行文件的技能,供处理容器化项目的开发者使用。它围绕 BuildKit 提供安全、可维护的 Dockerfile、忽略文件和构建配置指导。

主要功能

  1. 01检查仓库后再修改配置
  2. 02采用 BuildKit 缓存与密钥挂载
  3. 03建议加固基础镜像和多阶段构建
  4. 04固定外部镜像摘要
  5. 05创建 .dockerignore 与 Bake 配置
  6. 06规范 Compose 本地开发配置

适用场景

  1. 01为应用新建 Dockerfile
  2. 02审查并现代化现有构建文件
  3. 03减少构建上下文中的敏感文件
  4. 04整理可复现的本地及 CI 构建

安装

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

$npx skills add https://github.com/bretfisher/skills --skill docker-pro

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

详细介绍

Modern Docker 和 BuildKit 规则

创建、编辑、审查或现代化任何 Docker 构建或运行文件时,请使用此技能:Dockerfile、.dockerignore、docker-bake.hcl 和 compose.yaml。除非仓库中已有明确冲突的惯例,或用户明确要求采用不同的权衡,否则应坚定地坚持这些默认做法并主动应用。

这些规则假定使用 BuildKit 作为构建器(现代 Docker Engine 和 docker buildx 中的默认构建器)。正是这一假设,让我们可以使用以下所有现代前端功能,而无需针对旧版构建器添加兼容处理。

工作方式

  1. 编写前先检查仓库。识别语言/运行时、包管理器、锁定文件、构建输出目录以及任何现有 Docker 文件,确保输出符合实际情况,而不是套用通用模板。
  2. 优先采用正确且改动最小的方案,但如果正在编辑相关文件,就不要保留旧式模式(例如在 compose 中使用 version:、将密钥烘焙进层、使用不带缓存挂载的 apt-get)。
  3. 不要把基础镜像标签、包名、端口或构建命令臆造为事实。如果仓库没有提供某个值,请使用标注明确的占位符并说明这一点。尤其是镜像标签可能会变化;如果不确定某个标签是否存在,请告知用户应对照镜像仓库目录确认,而不要直接断言。
  4. 完成下方的交接检查清单,让用户能够维护每项改动。

Dockerfile 布局

如果把人们最常修改的内容(版本和配置)放在顶部可预测的位置,并让构建逻辑从上到下排列,Dockerfile 就更易于维护。请遵循以下顺序:

# syntax=docker/dockerfile:1          # 1. frontend directive — MUST be the very first line
ARG NODE_VERSION=22                   # 2. global version ARGs — one place to bump bases
ARG ALPINE_VERSION=3.22

FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS deps   # 3. all FROM stages, each named AS
FROM deps AS build
FROM cgr.dev/chainguard/node:latest AS runtime
# ... stage bodies follow, in build order ...
1. 前端指令放在第一行

每个 Dockerfile 都应以以下内容开头:

# syntax=docker/dockerfile:1

这会在构建时从 Docker Hub 获取最新的稳定 1.x Dockerfile 前端,从而使 Dockerfile 功能与引擎版本解耦——即使是较旧的 Docker Engine 也能理解最新语法。固定为 :1(而非 :1.0 或完整版本号)是有意为之:v1 系列保证不会有破坏性变更,因此可以免费获得新功能和修复。只有当用户明确需要前端本身逐字节可复现时,才固定到次要版本(例如 :1.7)。

它必须是第一行——位于注释和 ARG 之前。如果它不是第一行,BuildKit 就会忽略它,并悄然退回内置前端。

2. 在全局 ARG 中集中管理版本

在全局作用域(第一个 FROM 之前)将基础镜像版本声明为 ARG,让所有版本都集中在顶部的一个代码块中:

ARG NODE_VERSION=22
ARG ALPINE_VERSION=3.22

全局 ARG 在 FROM 行中有效。若要在阶段内部(例如在 RUN 中)使用它,还需在该阶段内重新声明一个不带默认值的 ARG NODE_VERSION——全局参数不会自动传入阶段主体。这样做的好处是:升级版本只需在顶部修改一行,而且可以在构建时通过 --build-arg NODE_VERSION=20 覆盖任何值,无需修改文件。

3. 将所有 FROM 阶段放在顶部,并为每个阶段命名

将每个构建阶段的 FROM 放在文件顶部附近,并使用明确的 AS <name>,然后再写较长的阶段主体。把 FROM 行放在一起,能让镜像关系图一目了然,也能将所有基础镜像引用集中在一处,与上面的版本 ARG 相配合。命名阶段还能提供稳定的 COPY --from=<name> 目标,并允许用户使用 --target <name> 构建单个阶段。

4. 在阶段顶部附近声明 ENV

将某个阶段的 ENV 声明集中放在该阶段顶部附近,不要散落在各个 RUN 之间。环境变量是人们最常查找的配置;将它们放在一起更便于查找,也能让缓存失效行为更可预测(ENV 变更会使其下方的所有内容失效)。

5. 使用 BuildKit 前端功能

由于这里假定使用 BuildKit,请优先采用以下功能,而不是旧式替代方案——它们速度更快、更安全,或两者兼具:

# Cache mounts: persist package-manager caches across builds (don't bloat layers)
RUN --mount=type=cache,target=/root/.npm npm ci

# Secret mounts: secrets are available during RUN but never written to a layer
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci

# Bind mounts: read context/another stage during RUN without COPYing it in
RUN --mount=type=bind,source=package.json,target=package.json npm ci

# COPY --link: layer is independent of its parent chain → better cache reuse
COPY --link ./dist /app/dist

# Heredocs: multi-line RUN without trailing backslashes
RUN <<EOF
set -eux
apt-get update
apt-get install -y --no-install-recommends curl
rm -rf /var/lib/apt/lists/*
EOF

# ADD --checksum: verify a remote artifact's integrity
ADD --checksum=sha256:24454f8...d68d https://example.com/app.tar.gz /opt/

基础镜像:默认采用加固版本

默认使用精简且经过加固的基础镜像。它们包含的程序包少得多,因此攻击面更小、需要修复的 CVE 更少,镜像也更小。只有当应用确实需要在运行时使用额外工具,且加固版本无法满足需求时,才选择功能齐全的通用基础镜像(Debian/Ubuntu/官方 alpine);并在这样做时说明原因。

两个免费且推荐的来源:

  • Chainguard Images — cgr.dev/chainguard/<name>。公共目录中的镜像无需身份验证即可拉取,因此是门槛最低的默认选择。大多数镜像有两种版本:
    • :latest-dev — 包含 shell 和 apk 程序包管理器。在需要安装软件的构建阶段使用。
    • :latest(以及 static)— distroless:没有 shell,也没有程序包管理器。在最终运行阶段使用。
  • Docker Hardened Images (DHI) — dhi.io/<name>。免费,但拉取前需要使用 Docker 账户执行一次 docker login dhi.io。标签格式如 dhi.io/python:3.13 或 dhi.io/python:3.12-alpine3.22,另有添加了 shell 和程序包管理器、供构建阶段使用的 -dev 版本。如果用户已经在使用 Docker 生态系统,或需要 Docker 签名的 SBOM/VEX 供应链元数据,优先选择 DHI。

多阶段构建模式让加固镜像更实用:在 -dev 构建阶段完成繁杂工作(编译器、安装程序包),然后只将构建产物通过 COPY --from 复制到没有 shell 或程序包管理器、因而无法被利用的 distroless 最终阶段。

# syntax=docker/dockerfile:1
ARG GO_VERSION=1.24

FROM cgr.dev/chainguard/go:latest-dev AS build
FROM cgr.dev/chainguard/static:latest AS runtime

FROM build AS compile
WORKDIR /src
COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
    --mount=type=cache,target=/go/pkg/mod \
    CGO_ENABLED=0 go build -o /app ./cmd

FROM runtime
COPY --from=compile /app /app
ENTRYPOINT ["/app"]

distroless 最终阶段没有 shell,因此 ENTRYPOINT/CMD 必须使用 exec 格式(["/app"],而不是 /app),并且 RUN/apt/apk 在那里都无法使用——这正是设计目的。由于各镜像的具体标签会有所不同且可能随时间变化,应将上述标签视为格式示例,而非绝对标准;必要时提醒用户对照目录确认。

为每个外部镜像固定摘要

为每个外部镜像引用固定标签和摘要,包括 FROM 行,以及会拉取注册表镜像的 COPY --from=<image>。node:24 或 :latest 这样的标签是可变的:注册表可以随时将其指向新内容,因此,同一个 Dockerfile 的两次构建可能生成不同的镜像。@sha256:... 摘要使用不可变内容寻址,因此能让构建可重现,并堵住供应链漏洞(获取的正是你审核过的字节,而不是之后推送的内容)。同时保留人类可读的标签和摘要,方便读者了解镜像的_内容_:

FROM node:24.11.1@sha256:9a2ed90cd91b1f3412affe080b62e69b057ba8661d9844e143a6bbd76a23260f

获取摘要时,如果已安装 regctl,优先使用它——它会查询注册表,而无需拉取整个镜像:

regctl manifest head node:24.11.1     # prints the digest for the current tag

如果没有 regctl,则改用 docker buildx imagetools inspect <image>:<tag>(同样无需完整拉取),或者拉取镜像并读取 docker inspect --format '{{index .RepoDigests 0}}' <image>:<tag>。不要臆造摘要——如果无法解析出真实摘要,就保留一个明确标记的 @sha256:<PIN-ME> 占位符,并告诉用户用于填入摘要的确切命令。

这与集中管理版本的 ARG 配合使用:将标签保存在一个 ARG 中以便阅读,并在相邻的 ARG 中保存摘要,这样整个引用就集中在顶部一处:

ARG NODE_VERSION=24.11.1
ARG NODE_DIGEST=sha256:9a2ed90cd91b1f3412affe080b62e69b057ba8661d9844e143a6bbd76a23260f
FROM node:${NODE_VERSION}@${NODE_DIGEST} AS build

为避免产生反效果,请注意以下两点:

  • 这只适用于外部镜像 URI。引用_命名内部阶段_的 COPY --from=build 无需摘要——它在构建过程中解析,不会从注册表拉取。
  • 固定摘要会锁定镜像,这正是目的——但这也意味着在更新摘要之前,你将无法获得上游安全补丁。提醒用户按计划让自动化工具(Dependabot/Renovate 的 Docker 支持)更新这些摘要,就像更新任何依赖项一样;不要手动编辑哈希,更不要为了“保持最新”而退回到不固定的标签。

始终创建 .dockerignore

每个构建上下文都应有一个 .dockerignore。它可以缩小发送给构建器的内容(加快构建速度、改善缓存),更重要的是,它是一项真正的安全措施:整个上下文都会发送给守护进程,因此排除 .git、.env 和密钥可以防止机密信息和历史记录泄漏到镜像中。

创建时,先以仓库的 .gitignore 为基础(如果存在,就复制其中的条目,以便也排除 node_modules、构建输出和 dist 等已忽略的杂项),然后添加 .gitignore 中不会包含的 Docker 专用条目:

# --- copied from .gitignore ---
# (paste the project's .gitignore contents here)

# --- Docker additions ---
.git
.gitignore
.dockerignore
Dockerfile*
compose*.yaml
docker-compose*.yml
docker-bake.hcl

# secrets & local env
.env
.env.*
*.pem
secrets/

# CI / docs / editor / OS cruft
.github/
README.md
.vscode/
.idea/
.DS_Store

.git 是最重要的新增项:它通常体积很大,而且包含完整历史记录(包括曾经提交过的所有机密信息),几乎永远不需要放进镜像中。

始终添加 Bake 文件

添加 docker-bake.hcl,将构建定义为可复现、受版本控制的配置,而不是冗长且不断偏离的 docker build 命令行。Bake(docker buildx bake)会读取该文件,使用一致的标签、平台和参数构建一个或多个目标;CI 也应调用它,以确保本地构建和 CI 构建保持一致。

variable "TAG" {
  default = "latest"
}

group "default" {
  targets = ["app"]
}

target "app" {
  context    = "."
  dockerfile = "Dockerfile"
  tags       = ["myapp:${TAG}"]
  # platforms = ["linux/amd64", "linux/arm64"]   # uncomment for multi-arch
}

使用 docker buildx bake(default 组)或 docker buildx bake app 进行构建。在运行时覆盖变量(TAG=v2 docker buildx bake),或按目标覆盖(--set app.platforms=linux/arm64)。使用 inherits 共享基础目标的设置,使用 matrix 将一个目标扩展为多个变体。请注意,如果不带 -f,docker buildx bake 会优先自动发现 compose.yaml,然后才查找 docker-bake.hcl,并将服务的 build: 块转换为目标。因此,如果仓库中同时存在这两个文件,请在 CI 中使用 -f docker-bake.hcl 指定所需文件。

使用 Compose 进行本地开发

如果是首次设置且尚无 Compose 文件,请完成所需的构建文件,并在最终答复中提出添加 compose.yaml。在获得批准前,先不要创建 Compose 文件,让用户决定本地开发的范围。如果用户暂时无法回复,请在下次回复时附上该提议。如果 Compose 文件已存在,则在原处进行现代化改造。

约定:

  • 将文件命名为 compose.yaml(Compose Spec 的规范文件名),而不是 docker-compose.yml。
  • 不要使用顶层 version: 键。 它在 Compose V2 中已过时,现在会发出警告;修改任何文件时都应将其移除。
  • 源代码优先使用文件同步,而非绑定挂载。 传统绑定挂载(./src:/app/src)会使容器路径依赖于主机目录结构,还可能速度较慢或遇到权限问题。现代的 develop.watch 机制则会将变更的文件 同步 到容器中,速度更快、跨平台兼容,而且可以为每种路径变更选择相应操作。
services:
  web:
    build: .
    ports:
      - "3000:3000"
    develop:
      watch:
        # sync: push live source changes into the running container (hot reload)
        - action: sync
          path: ./src
          target: /app/src
          ignore:
            - node_modules/
        # rebuild: dependency manifests / compiled languages need a fresh image
        - action: rebuild
          path: package.json
        # sync+restart: config the process only reads at startup
        - action: sync+restart
          path: ./config.yml
          target: /app/config.yml

使用 docker compose watch 启动。根据变更所需的操作进行选择:可热重载的源代码使用 sync,锁文件或编译后的代码使用 rebuild,仅在启动时读取的配置使用 sync+restart。

完成时

在面向用户的最终答复中完成以下检查,确保用户无需打开其他文档也能维护这些文件:

  • 对每项已采用的默认设置或现代化改造,分别提供一行“变更 — 原因”。涵盖前端、基础镜像、摘要固定、缓存挂载、运行时用户、.dockerignore、Bake,以及使用时的每项 Compose 变更。原因应说明其好处;仅罗列设置并不完整。
  • 提供准确的构建和运行命令,以及验证结果和未解决的占位内容,以便用户复现操作。
  • 如果是首次设置,请附上上述 Compose 提议,供用户批准下一步。

评论

支持 Markdown