docker-build-strategies

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Docker Build Strategies

Docker 构建策略

Overview

概述

This skill provides rules and patterns for writing and reviewing production-quality Dockerfiles. Apply it when the main task is image-build quality: multi-stage builds, cache behavior, non-root execution, build context hygiene, and runtime image size.
本技能提供编写和审查生产级 Dockerfile 的规则与模式。当主要任务是镜像构建质量时应用:多阶段构建、缓存行为、非 root 执行、构建上下文卫生和运行时镜像体积。

When to use this skill

何时使用本技能

Activate this skill when:
  • Creating a new Dockerfile for any language or framework
  • Optimizing an existing Dockerfile for size, speed, or security
  • Reviewing a Dockerfile for best-practice compliance
  • Adding a
    .dockerignore
    file to a project
在以下情况下激活本技能:
  • 为任何语言或框架创建新的 Dockerfile
  • 优化现有 Dockerfile 的体积、速度或安全性
  • 审查 Dockerfile 是否符合最佳实践
  • 为项目添加
    .dockerignore
    文件

Do not use this skill when

不应使用本技能的情况

Do not use this skill when:
  • The project has no Docker setup yet and the main need is a first-pass scaffold
  • The main task is wiring services together in
    compose.yaml
  • The main task is debugging Compose startup ordering, networking, or development overrides
在以下情况下不应使用本技能:
  • 项目尚无 Docker 配置,主要需求是初步脚手架搭建
  • 主要任务是在
    compose.yaml
    中编排服务
  • 主要任务是调试 Compose 启动顺序、网络或开发覆盖配置

Core guidance

核心指导

Multi-stage builds

多阶段构建

Use multi-stage builds when the project has a build step or when build-time dependencies differ from runtime. Separate build-time dependencies from the runtime image.
  1. Name every stage explicitly (
    FROM ... AS build
    ,
    FROM ... AS runtime
    ).
  2. Use the smallest appropriate base for the runtime stage:
    distroless
    ,
    alpine
    , or
    slim
    variants.
  3. Copy only the final artifact into the runtime stage with
    COPY --from=build
    .
  4. Use
    COPY --link
    when copying from a prior stage or adding static files — it improves cache reuse by making the COPY independent of previous layers.
See
references/multi-stage-builds.md
for language-specific patterns (Go, Node, Python, Java).
当项目有构建步骤,或构建时依赖与运行时依赖不同时,使用多阶段构建。将构建时依赖与运行时镜像分离。
  1. 显式命名每个阶段(
    FROM ... AS build
    、
    FROM ... AS runtime
    )。
  2. 为运行时阶段使用最小的合适基础镜像:
    distroless
    、
    alpine
    或
    slim
    变体。
  3. 使用
    COPY --from=build
    仅将最终产物复制到运行时阶段。
  4. 从先前阶段复制或添加静态文件时使用
    COPY --link
    ——它使 COPY 独立于之前的层,从而提高缓存复用。
参见
references/multi-stage-builds.md
了解各语言(Go、Node、Python、Java)的特定模式。

Layer caching

层缓存

Order Dockerfile instructions from least-frequently-changed to most-frequently-changed.
  1. Place dependency manifests (
    package.json
    ,
    go.mod
    ,
    requirements.txt
    ) and install steps before copying application source code.
  2. Use BuildKit cache mounts for package manager caches:
    • 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 ...
  3. Pin base image tags to a specific version or digest — never use
    latest
    in production.
  4. Combine related
    RUN
    commands with
    &&
    to reduce layer count, but keep logically distinct steps separate for cache granularity.
See
references/layer-caching.md
for detailed cache invalidation rules and cache mount patterns.
按从“最不常变更”到“最常变更”的顺序排列 Dockerfile 指令。
  1. 在复制应用源码之前,放置依赖清单(
    package.json
    、
    go.mod
    、
    requirements.txt
    )和安装步骤。
  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 ...
  3. 将基础镜像标签固定到特定版本或摘要——生产环境中绝不使用
    latest
    。
  4. 使用
    &&
    合并相关的
    RUN
    命令以减少层数,但将逻辑上独立的步骤分开以保留缓存粒度。
参见
references/layer-caching.md
了解详细的缓存失效规则和缓存挂载模式。

Build secrets and SSH access

构建密钥与 SSH 访问

Never bake credentials into the image. Use BuildKit secrets and SSH mounts so credentials are available only during the specific
RUN
step that needs them, and never persist in any layer or
docker history
output.
  1. Do NOT pass credentials through
    ARG
    or
    ENV
    . Both end up in the image layers and are inspectable via
    docker history
    .
  2. Do NOT
    COPY
    credential files into the build context:
    .npmrc
    ,
    .pypirc
    ,
    .netrc
    ,
    pip.conf
    , Maven
    settings.xml
    ,
    .env
    , cloud credentials (
    ~/.aws/credentials
    ,
    ~/.config/gcloud/
    , service-account JSON files,
    ~/.azure/
    ), secret-manager tokens (
    ~/.vault-token
    ), package-registry tokens (
    ~/.cargo/credentials.toml
    ), TLS keys (
    *.pem
    ,
    *.p12
    ),
    kubeconfig
    , SSH keys (
    id_rsa
    ,
    id_dsa
    ,
    id_ed25519
    ,
    id_ecdsa
    ). Even when the final stage does not copy them forward, they live in intermediate layers and the build cache.
  3. Do NOT echo, write, or expand the secret value inside a
    RUN
    command in a way that persists it to a layer or emits it to build logs. Access the secret file (e.g.,
    /run/secrets/<id>
    , or directly via the mount
    target=
    ) — never
    echo "$(cat /run/secrets/X)"
    , never substitute it into a shell argument that will be logged with
    --progress=plain
    .
  4. Use
    RUN --mount=type=secret
    for package manager registry credentials:
    dockerfile
    RUN --mount=type=secret,id=npmrc,target=/root/.npmrc,required=false \
        --mount=type=cache,target=/root/.npm \
        npm ci --omit=dev
    The secret is available only inside that
    RUN
    , never written to a layer. Use
    required=true
    when the build will always need the credential (e.g., all packages come from a private registry, so missing the secret should fail the build immediately); use
    required=false
    only when the secret is optional (the build can succeed with public packages alone).
  5. Use
    RUN --mount=type=ssh
    for fetching private Git repositories or modules. The build container has no
    known_hosts
    by default — populate it inside the same
    RUN
    :
    dockerfile
    RUN --mount=type=ssh \
        mkdir -p -m 0700 /root/.ssh && \
        ssh-keyscan github.com >> /root/.ssh/known_hosts && \
        git clone git@github.com:org/private-repo.git
    Do NOT use
    StrictHostKeyChecking=no
    as a shortcut — it disables host-key verification entirely.
    ssh-keyscan
    pins the known fingerprint at build time.
  6. Invoke buildx with the secret and SSH sources:
    bash
    # Ensure an SSH agent is running with the key loaded (or use --ssh default=<key-file>):
    eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519
    
    docker buildx build \
        --secret id=npmrc,src=$HOME/.npmrc \
        --ssh default \
        .
    Alternatively, pass the key file directly without an agent:
    --ssh default=$HOME/.ssh/id_ed25519
    .
  7. .dockerignore
    exclusions of
    .env
    and credential files are defense in depth, not the primary mechanism — keep them, but do not rely on them as your only protection.
See
references/multi-stage-builds.md
for per-language patterns (npm, pip, Maven, Go
GOPRIVATE
).
切勿将凭据固化到镜像中。使用 BuildKit secrets 和 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/
    )、secret-manager 令牌(
    ~/.vault-token
    )、软件包仓库令牌(
    ~/.cargo/credentials.toml
    )、TLS 密钥(
    *.pem
    、
    *.p12
    )、
    kubeconfig
    、SSH 密钥(
    id_rsa
    、
    id_dsa
    、
    id_ed25519
    、
    id_ecdsa
    )。即使最终阶段没有向前复制它们,它们仍会存在于中间层和构建缓存中。
  3. 不要在
    RUN
    命令中以会持久化到层或输出到构建日志的方式 echo、写入或展开密钥值。要访问密钥文件(例如
    /run/secrets/<id>
    ,或直接通过挂载
    target=
    )——绝不
    echo "$(cat /run/secrets/X)"
    ,绝不将其替换到会被
    --progress=plain
    记录的 shell 参数中。
  4. 使用
    RUN --mount=type=secret
    处理包管理器仓库凭据:
    dockerfile
    RUN --mount=type=secret,id=npmrc,target=/root/.npmrc,required=false \
        --mount=type=cache,target=/root/.npm \
        npm ci --omit=dev
    该 secret 仅在该
    RUN
    内可用,绝不会写入层。当构建始终需要该凭据时使用
    required=true
    (例如所有包都来自私有仓库,缺少 secret 应立即失败);仅在 secret 可选时使用
    required=false
    (仅使用公共包也可成功构建)。
  5. 使用
    RUN --mount=type=ssh
    获取私有 Git 仓库或模块。构建容器默认没有
    known_hosts
    ——请在同一
    RUN
    中填充:
    dockerfile
    RUN --mount=type=ssh \
        mkdir -p -m 0700 /root/.ssh && \
        ssh-keyscan github.com >> /root/.ssh/known_hosts && \
        git clone git@github.com:org/private-repo.git
    不要将
    StrictHostKeyChecking=no
    作为捷径——它会完全禁用主机密钥验证。
    ssh-keyscan
    会在构建时固定已知指纹。
  6. 使用 secret 和 SSH 源调用 buildx:
    bash
    # Ensure an SSH agent is running with the key loaded (or use --ssh default=<key-file>):
    eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519
    
    docker buildx build \
        --secret id=npmrc,src=$HOME/.npmrc \
        --ssh default \
        .
    或者,无需 agent 直接传入密钥文件:
    --ssh default=$HOME/.ssh/id_ed25519
    。
  7. .dockerignore
    对
    .env
    和凭据文件的排除是纵深防御,而非主要机制——保留它们,但不要将其作为唯一保护。
参见
references/multi-stage-builds.md
了解各语言(npm、pip、Maven、Go
GOPRIVATE
)的特定模式。

.dockerignore

.dockerignore

Always generate a
.dockerignore
alongside the Dockerfile. Exclude:
  • .git/
    ,
    .github/
    ,
    .vscode/
    ,
    .idea/
  • node_modules/
    ,
    __pycache__/
    ,
    .venv/
    ,
    vendor/
    (when rebuilt in the build stage)
  • *.md
    ,
    LICENSE
    ,
    docs/
  • Build outputs, test artifacts, and IDE configs
  • .env
    files and any secrets
See
assets/dockerignore-example
for a comprehensive template.
始终在 Dockerfile 旁边生成
.dockerignore
。排除:
  • .git/
    、
    .github/
    、
    .vscode/
    、
    .idea/
  • node_modules/
    、
    __pycache__/
    、
    .venv/
    、
    vendor/
    (在构建阶段会重新构建时)
  • *.md
    、
    LICENSE
    、
    docs/
  • 构建输出、测试产物和 IDE 配置
  • .env
    文件和任何密钥
参见
assets/dockerignore-example
获取完整的模板。

Non-root user

非 root 用户

Always configure the final image to run as a non-root user.
  1. Create a dedicated user and group in the runtime stage:
    dockerfile
    RUN addgroup --system --gid 1001 appgroup && \
        adduser --system --uid 1001 --ingroup appgroup appuser
  2. Set ownership on application files:
    COPY --from=build --chown=appuser:appgroup /app /app
  3. When combining
    --chown
    with
    COPY --link
    , always use the numeric UID:GID you assigned (e.g.,
    --chown=1001:1001
    if you used
    --uid 1001 --gid 1001
    above), not named users.
    --link
    creates an independent layer where named users from prior
    RUN
    instructions are not available.
  4. Place the
    USER appuser
    instruction after all file operations and before
    ENTRYPOINT
    /
    CMD
    .
  5. On distroless images, use the built-in nonroot user:
    USER nonroot:nonroot
    .
始终将最终镜像配置为以非 root 用户运行。
  1. 在运行时阶段创建专用用户和组:
    dockerfile
    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. 在所有文件操作之后、
    ENTRYPOINT
    /
    CMD
    之前放置
    USER appuser
    指令。
  5. 在 distroless 镜像上,使用内置的 nonroot 用户:
    USER nonroot:nonroot
    。

Image size optimization

镜像体积优化

  1. Prefer
    FROM scratch
    (Go static binaries), distroless, or Alpine-based images for the runtime stage.
  2. Remove package manager caches in the same
    RUN
    layer that installs packages:
    apt-get install -y ... && rm -rf /var/lib/apt/lists/*
  3. Do not install documentation, man pages, or debug tools in the runtime image.
  4. Use
    .dockerignore
    aggressively to minimize the build context.
  1. 运行时阶段优先选择
    FROM scratch
    (Go 静态二进制)、distroless 或 Alpine 基础镜像。
  2. 在与安装包相同的
    RUN
    层中删除包管理器缓存:
    apt-get install -y ... && rm -rf /var/lib/apt/lists/*
  3. 不要在运行时镜像中安装文档、man 手册或调试工具。
  4. 积极使用
    .dockerignore
    来最小化构建上下文。

General rules

通用规则

  • Always include a
    # syntax=docker/dockerfile:1
    directive as the first line to enable BuildKit features.
  • Set
    WORKDIR
    before any
    COPY
    or
    RUN
    instructions — never rely on the default
    /
    .
  • Prefer
    ENTRYPOINT
    with exec form (
    ["binary"]
    ) over shell form.
  • Add
    EXPOSE
    to document the listening port.
  • Add metadata labels:
    LABEL org.opencontainers.image.source=...
  • 始终在第一行包含
    # syntax=docker/dockerfile:1
    指令以启用 BuildKit 特性。
  • 在任何
    COPY
    或
    RUN
    指令之前设置
    WORKDIR
    ——绝不要依赖默认的
    /
    。
  • 优先使用 exec 形式的
    ENTRYPOINT
    (
    ["binary"]
    )而非 shell 形式。
  • 添加
    EXPOSE
    以记录监听端口。
  • 添加元数据标签:
    LABEL org.opencontainers.image.source=...

Related skills

相关技能

  • For first-time Docker project scaffolding and deciding which files to create, use
    docker-project-foundations
    .
  • For service dependencies, health checks, overrides, networks, and volume patterns, use
    docker-compose-patterns
    .
  • For destructive Docker CLI commands (
    docker system prune
    ,
    docker rm -f
    , image/network/builder pruning) and a cross-product index of destructive-command guardrails, use
    docker-destructive-guardrails
    .
  • 首次 Docker 项目脚手架搭建和决定创建哪些文件时,使用
    docker-project-foundations
    。
  • 服务依赖、健康检查、覆盖配置、网络和卷模式,使用
    docker-compose-patterns
    。
  • 破坏性 Docker CLI 命令(
    docker system prune
    、
    docker rm -f
    、镜像/网络/构建器清理)以及跨产品破坏性命令防护索引,使用
    docker-destructive-guardrails
    。

References

参考

  • references/multi-stage-builds.md
    — Language-specific multi-stage patterns for Go, Node.js, Python, and Java
  • references/layer-caching.md
    — Deep dive on layer ordering, cache invalidation, and BuildKit cache mounts
  • references/multi-stage-builds.md
    — Go、Node.js、Python 和 Java 的语言特定多阶段模式
  • references/layer-caching.md
    — 层排序、缓存失效和 BuildKit 缓存挂载的深入探讨

Assets

资源

  • assets/Dockerfile.go
    — Multi-stage Go build with distroless runtime and non-root user
  • assets/Dockerfile.nodejs
    — Multi-stage Node.js build with proper layer caching and non-root user
  • assets/Dockerfile.python
    — Python build with virtual env, layer ordering, and non-root user
  • assets/dockerignore-example
    — Comprehensive
    .dockerignore
    template
  • assets/Dockerfile.go
    — 使用 distroless 运行时和非 root 用户的多阶段 Go 构建
  • assets/Dockerfile.nodejs
    — 具有正确层缓存和非 root 用户的多阶段 Node.js 构建
  • assets/Dockerfile.python
    — 使用虚拟环境、层排序和非 root 用户的 Python 构建
  • assets/dockerignore-example
    — 全面的
    .dockerignore
    模板

Scripts

脚本

  • scripts/verify-build.sh
    — Builds the image, reports size and configured user.
    bash
    bash scripts/verify-build.sh [--help] [IMAGE_NAME]
    Exit status is
    0
    when all Docker commands succeed or help is requested, the failing Docker command's non-zero status when verification fails, and
    2
    for invalid arguments.
  • scripts/verify-build.sh
    — 构建镜像,报告体积和已配置的用户。
    bash
    bash scripts/verify-build.sh [--help] [IMAGE_NAME]
    当所有 Docker 命令成功或请求帮助时退出状态为
    0
    ;当验证失败时,退出状态为失败 Docker 命令的非零状态;参数无效时为
    2
    。

Checks

检查

  • checks/verification.md
    — Detailed verification runbook for manual review.
  • checks/verification.md
    — 用于手动审查的详细验证运行手册。