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 与明确的 WORKDIR;HEALTHCHECK 与业务就绪语义一致(勿用仅检查进程存在而忽略依赖就绪)。
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