Dockerfile 最佳实践

指导 Agent 使用多阶段构建、`.dockerignore`、固定基础镜像 digest 与最小运行时依赖,并配置 HEALTHCHECK 与 USER;分层顺序决定缓存命中率与可扫描性。

SKILL 要求把变更频繁层置后、依赖安装与复制源码分层;apt/apk 安装合并 RUN 并清理缓存;不在镜像中 bake 密钥,用 build args 时注意历史层泄露。

指定工作目录、暴露端口文档化、ENTRYPOINT 与 CMD 的职责分离;信号转发与 PID 1 若需 tini,写明基础镜像是否已包含。

与扫描策略对齐:选择有安全维护的基础标签、定期重建;非 root UID/GID 与只读根文件系统(若编排要求)在步骤中引用。

  • BuildKit 缓存挂载与 `--mount=type=secret` 的敏感构建数据。
  • 多架构:buildx 与平台矩阵在 CI 技能中交叉引用。
  • 本地与 CI 相同的 docker compose 覆盖文件约定。

分层与缓存(layers)

.dockerignore 与「仅复制锁文件 / 清单」的 COPY 放在高频变更源码之前;同一 RUN 内合并包管理器安装与缓存清理,减少层数与体积。

  • Pin 基础镜像:优先 digest(image@sha256:…),文档化更新流程。
  • 避免 COPY . . 过早:大上下文与无关文件会击穿缓存。
  • 文档化 ARG 默认值与构建时 secret 的边界,避免层历史中残留敏感字符串。

.dockerignore 完整示例文件(Node.js 项目):

# .dockerignore — 减少构建上下文体积,避免击穿缓存
# 版本控制
.git
.gitignore
.gitattributes

# 依赖目录(在容器内重新安装)
node_modules
npm-debug.log*
yarn-error.log*

# 构建产物(多阶段构建会重新生成)
dist
build
.next
out

# 测试与覆盖率
coverage
__tests__
*.test.ts
*.spec.ts
jest.config.*

# 文档
*.md
docs/
LICENSE

# 本地开发配置
.env
.env.local
.env.*.local
docker-compose.override.yml

# CI 配置
.github
.gitlab-ci.yml
Jenkinsfile

# 编辑器
.vscode
.idea
*.swp
*.swo

多阶段构建

构建阶段包含编译工具链、测试依赖与缓存;最终阶段仅 COPY --from=… 产物与运行时库,缩小攻击面与镜像体积。

  • 阶段命名清晰(AS builder / AS runtime),避免隐式 FROM 链难以维护。
  • 仅将运行所需文件拷入最终镜像;调试工具留在 dev compose 覆盖层。
  • 多阶段仍要遵守分层:在 builder 内同样先依赖后源码。

完整的 Node.js 多阶段 Dockerfile(含所有最佳实践):

# syntax=docker/dockerfile:1
# Node.js 多阶段构建 — 所有最佳实践

# ---------- 阶段 1:deps(仅安装生产依赖) ----------
FROM node:20-alpine@sha256:a7394f8b2e2a4e4aef9f6082c84f6d7e3b1d2b1 AS deps
WORKDIR /app

# 先复制锁文件,利用缓存层
COPY package.json package-lock.json ./
RUN npm ci --omit=dev --prefer-offline \
    && npm cache clean --force

# ---------- 阶段 2:builder(构建产物) ----------
FROM node:20-alpine@sha256:a7394f8b2e2a4e4aef9f6082c84f6d7e3b1d2b1 AS builder
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --prefer-offline         # 含 devDependencies 用于构建

COPY . .                             # 源码置后,避免依赖层失效
RUN npm run build \
    && npm prune --production        # 删除 devDependencies

# ---------- 阶段 3:runtime(最小运行时镜像) ----------
FROM node:20-alpine@sha256:a7394f8b2e2a4e4aef9f6082c84f6d7e3b1d2b1 AS runtime

# 安全:创建非 root 用户
RUN addgroup --system --gid 1001 nodejs \
    && adduser --system --uid 1001 --ingroup nodejs appuser

WORKDIR /app

# 仅从 builder 阶段复制产物与依赖
COPY --from=builder --chown=appuser:nodejs /app/dist ./dist
COPY --from=builder --chown=appuser:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:nodejs /app/package.json ./

ENV NODE_ENV=production \
    PORT=3000

USER appuser

EXPOSE 3000

# HEALTHCHECK:与业务就绪语义一致
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"

# ENTRYPOINT 处理信号;CMD 提供可覆盖参数
ENTRYPOINT ["node"]
CMD ["dist/server.js"]

完整的 Python 多阶段 Dockerfile(FastAPI 示例):

# syntax=docker/dockerfile:1
# Python 多阶段构建 — FastAPI / 通用 Python 服务

# ---------- 阶段 1:builder ----------
FROM python:3.12-slim@sha256:abc123 AS builder

RUN pip install --upgrade pip \
    && pip install build wheel

WORKDIR /app
COPY pyproject.toml requirements.txt ./

# BuildKit secret 挂载:私有 registry token 不进 layer 历史
RUN --mount=type=secret,id=pip_token \
    pip install --no-cache-dir \
      --extra-index-url "https://$(cat /run/secrets/pip_token)@private.pypi.example.com/simple/" \
      -r requirements.txt \
      --target /install

# ---------- 阶段 2:runtime ----------
FROM python:3.12-slim@sha256:abc123 AS runtime

RUN groupadd --gid 1001 appgroup \
    && useradd --uid 1001 --gid appgroup --no-create-home appuser

WORKDIR /app

# 仅复制安装好的包和应用代码
COPY --from=builder /install /usr/local/lib/python3.12/site-packages/
COPY --chown=appuser:appgroup . .

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PORT=8000

USER appuser
EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"

ENTRYPOINT ["uvicorn", "app.main:app"]
CMD ["--host", "0.0.0.0", "--port", "8000"]

运行期、安全与信号

使用非特权 USER 与明确的 WORKDIRHEALTHCHECK 与业务就绪语义一致(勿用仅检查进程存在而忽略依赖就绪)。

  • ENTRYPOINT 负责常驻与信号行为;CMD 提供默认参数,便于 compose/k8s 覆盖。
  • Java/Node 等需正确处理 SIGTERM 时,说明是否使用 tini 或等价入口。
  • EXPOSE 仅作文档;真实端口由编排声明。

镜像流水线(skill-flow-block)

  [ 上下文:.dockerignore + 锁文件策略 ]
        │
        ▼
  [ Dockerfile:分层(依赖 → 源码)或多阶段 FROM ]
        │
        ▼
  ┌─────────────┐     BuildKit 缓存 / secret 挂载;平台矩阵(按需)
  │ docker build │──── 产物:带 digest 的标签、SBOM(策略内)
  └─────────────┘
        │
        ▼
  ┌─────────────┐     镜像扫描、许可证与基线策略
  │  安全扫描    │──── 高危项阻塞或记录例外工单
  └─────────────┘
        │
        ▼
  [ 注册表推送 → 编排引用同一 digest / 不可变标签 ]

Agent 产出 Dockerfile 时同步列出「会失效缓存的 COPY 顺序」与「最终阶段仅含运行时文件」的检查项,便于审查与复现构建。

Trivy 安全扫描命令与 Docker Compose 开发环境配置:

# trivy 镜像扫描:检查 CVE 漏洞与 secrets 泄露
trivy image --exit-code 1 \
  --severity HIGH,CRITICAL \
  --ignore-unfixed \
  myapp:1.2.3-abc1234

# 扫描并生成 SARIF 报告(上传到 GitHub Security)
trivy image \
  --format sarif \
  --output trivy-results.sarif \
  myapp:1.2.3-abc1234

# GitHub Actions 集成示例
# - name: Scan image with Trivy
#   uses: aquasecurity/trivy-action@master
#   with:
#     image-ref: myapp:${{ steps.tag.outputs.tag }}
#     format: 'sarif'
#     output: 'trivy-results.sarif'
#     severity: 'HIGH,CRITICAL'
#     exit-code: '1'
#     ignore-unfixed: true

Docker Compose 开发环境配置示例(compose.yml + compose.override.yml):

# compose.yml — 基础定义(可在 CI/CD 中使用)
services:
  app:
    build:
      context: .
      target: runtime        # 指定多阶段的目标阶段
    image: myapp:dev
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: development
      DATABASE_URL: postgres://user:pass@db:5432/myapp
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
      interval: 5s
      timeout: 3s
      retries: 5

volumes:
  db-data:

# compose.override.yml — 本地开发覆盖(不进 git)
# services:
#   app:
#     build:
#       target: builder     # 开发阶段含工具链
#     volumes:
#       - .:/app            # 挂载源码以热重载
#       - /app/node_modules # 不覆盖容器内 node_modules
#     command: npm run dev

图层顺序沙盘

勾选栈型与选项,生成本页专用的「建议指令顺序」备忘(示意,需按语言与基础镜像微调);用于 SKILL 自检或与人工审查对齐。

依赖 / 构建栈(单选)
选项

建议图层顺序(自上而下)


              

沙盘不生成完整 Dockerfile;将输出与团队基础镜像、CI 变量及 HEALTHCHECK 拼成终稿。密钥仅经 secret 挂载或运行时注入,勿写进 ARG 默认值。

SKILL 片段

---
name: dockerfile-best-practices
description: 多阶段、非 root、可扫描的容器镜像最佳实践
tags: [docker, container, security, devops]
---
# 基础镜像与上下文
1. Pin 基础镜像到完整 digest(image@sha256:...),文档化更新流程
2. .dockerignore 排除 .git、node_modules、.env、测试文件、文档
3. 基础镜像选择:slim/alpine/distroless 按场景选择最小攻击面

# 分层缓存策略
4. 先 COPY 锁文件(package-lock.json / go.sum / requirements.txt)再安装依赖
5. 再 COPY 源码:避免源码改动击穿依赖缓存层
6. apt/apk 安装在单 RUN 中合并并清理 cache(&& rm -rf /var/cache/apk/*)
7. BuildKit secret 挂载(--mount=type=secret):私有 registry token 不进层历史

# 多阶段构建
8. builder 阶段含工具链与 devDependencies;runtime 阶段仅 COPY 产物
9. 阶段命名清晰:AS deps / AS builder / AS runtime
10. 中间阶段可单独 target 构建(docker build --target builder)

# 运行时安全
11. 最终阶段创建并切换非 root 用户(adduser + USER)
12. HEALTHCHECK 与业务就绪语义一致(不仅检测进程存在)
13. ENTRYPOINT 处理信号;CMD 提供可覆盖默认参数
14. EXPOSE 仅作文档,真实端口由编排(k8s/compose)声明

# 安全扫描
15. trivy image 扫描 HIGH/CRITICAL CVE,--exit-code 1 阻塞 CI
16. 定期重建(每周)以获取基础镜像安全更新
17. 多架构:docker buildx --platform linux/amd64,linux/arm64

返回技能库 更多技能入口