Skip to content
返回部署

Docker 容器化最佳实践

生产容器的镜像分层、distroless 基础、多架构构建与供应链安全。

容器进阶

为什么

基于 node:20、root 用户、未扫描依赖的 1.5GB 镜像是待发生的安全事件。严谨容器化削减镜像 80%,通过 distroless 减少 90% CVE 面,并支持多架构(amd64+arm64)跨平台部署。

何时使用

应用于任何进生产的容器,尤其需 SBOM 和签名镜像的受监管行业(金融、医疗)。本地开发镜像跳过加固(用 docker-compose 覆盖)。

如何做

1. 层顺序 — COPY package*.json + RUN npm ci 在 COPY . . 前,跨代码变更缓存依赖 2. Distroless 基础 — gcr.io/distroless/nodejs20-debian12 无 shell、无包管理器,更小更安全 3. 非 root 用户 — 在 builder 创建 USER,runner 中设置;容器逃逸 ≠ host root 4. 多架构 — docker buildx build --platform linux/amd64,linux/arm64 --push(支持 Apple Silicon + Graviton) 5. SBOM — docker buildx build --sbom=true 生成 SPDX 清单,作为 OCI 工件附到镜像 6. 签名 — cosign sign --key cosign.key myregistry/app:v1(Sigstore,Kubernetes 可策略验证) 7. 扫描 — trivy image --severity HIGH,CRITICAL myapp:v1,CI 发现则失败;用 .trivyignore 接受风险 8. 运行时加固 — docker run --read-only --cap-drop ALL --security-opt no-new-privileges

示例

# Dockerfile — 加固,多架构就绪 # syntax=docker/dockerfile:1.6(支持 --sbom、--provenance) FROM node:20-alpine AS deps WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev --ignore-scripts FROM gcr.io/distroless/nodejs20-debian12 AS runner WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY --chown=nonroot:nonroot dist ./dist COPY package*.json ./ USER nonroot:nonroot EXPOSE 3000 ENTRYPOINT ["node", "dist/server.js"] # .dockerignore — 保持构建上下文小 node_modules .git *.md .env* dist coverage # 构建 + 推多架构 + SBOM + 签名 # docker buildx create --use --name mybuilder # docker buildx build \ # --platform linux/amd64,linux/arm64 \ # --sbom=true --provenance=true \ # -t myregistry/app:v1 --push . # cosign sign --key cosign.key myregistry/app:v1 # trivy image --severity HIGH,CRITICAL --exit-code 1 myregistry/app:v1 # 运行时 — 只读 rootfs,弃所有 cap # docker run --read-only --tmpfs /tmp \ # --cap-drop ALL --security-opt no-new-privileges \ # -p 3000:3000 myregistry/app:v1

常见问题

  • ⚠Distroless 破坏调试 — 无 shell 可 exec,用 debug 变体或临时 sidecar
  • ⚠多架构构建慢 3 倍 — amd64 主机上 QEMU 模拟 arm64 慢,用原生 arm64 builder
  • ⚠cosign 验证未强制 — 镜像已签名但 K8s 也允许未签名,加 Kyverno/OPA 策略
  • ⚠Trivy 在 distroless 失败 — 无包管理器查询,用 --scanners vuln 配 OS DB

最佳实践

  • ✓按摘要固定基础镜像 — FROM node:20-alpine@sha256:abc... 而非 :20-alpine,防供应链替换
  • ✓用 BuildKit 缓存挂载 — RUN --mount=type=cache,target=/root/.npm 快速重建
  • ✓签名且集群准入验证 — Kyverno 策略部署时拒绝未签名镜像
  • ✓.dockerignore 严格 — 排除测试夹具、.git、node_modules,上下文缩 10 倍

常见问题解答

多阶段构建——为什么和怎么做?

分离构建环境(含 devDependencies、编译器)和运行时镜像。阶段 1 构建,仅复制 dist/ 到阶段 2(FROM node:18-alpine)。最终镜像无源码或构建工具——更小更安全。

如何让密钥不进 Docker 镜像?

绝不 COPY .env 进镜像或 ARG 密钥——镜像层可见。用 Docker BuildKit secrets(--secret)或运行时挂载(-e 或 --env-file)。用 trivy 扫描验证无密钥泄漏。

为什么固定镜像版本而非用 :latest?

:latest 无警告变化——基础镜像更新时构建断裂。固定到特定摘要(node:18-alpine@sha256:abc...)实现可复现构建。在计划 PR 中有意更新固定。

如何优化 Docker 层缓存?

Dockerfile 命令从最少变到最多变排序。先复制 package.json(少变)再复制源码(每次提交变)。每个变化层使其下所有层失效。

CMD 和 ENTRYPOINT 有什么区别?

ENTRYPOINT 是固定命令(如 node server.js);CMD 是可覆盖默认参数(docker run <img> --port 4000)。容器为单一可执行文件用 ENTRYPOINT,通用基础用 CMD。