正在加载,请稍候…

Docker 镜像安全:多阶段构建、非 root 用户、镜像扫描与 Dockerfile 最佳实践

强化生产级 Docker 镜像:使用多阶段构建缩小攻击面,以非 root 用户运行容器,集成 Trivy 进行漏洞扫描,并遵循 Dockerfile 安全最佳实

Docker 镜像安全:多阶段构建、非 root 用户、镜像扫描与 Dockerfile 最佳实践

Docker 镜像安全:多阶段构建、非 root 用户、镜像扫描与 Dockerfile 最佳实践

容器安全始于镜像。一个构建不当的 Docker 镜像可能使生产环境暴露于漏洞、权限提升和供应链攻击之下。本指南涵盖安全镜像开发的完整生命周期,从编写安全的 Dockerfile 到在 CI/CD 中自动化漏洞扫描。

最小攻击面原则

镜像中的每个包、二进制文件和库都是潜在的攻击向量。镜像越小,可能包含的漏洞就越少。

基础镜像 压缩后大小 说明
ubuntu:24.04 ~30 MB 完整操作系统
debian:bookworm-slim ~25 MB 精简操作系统
alpine:3.19 ~3 MB 最小化 Linux
gcr.io/distroless/static ~2 MB 无 shell
scratch 0 MB 空基础镜像

Distroless 镜像仅包含应用程序及其运行时依赖,没有 shell、没有包管理器、没有攻击者可利用的工具。

Docker 镜像安全:多阶段构建、非 root 用户、镜像扫描与 Dockerfile 最佳实践 示意图

多阶段构建

Node.js 示例

# 阶段 1:安装生产依赖
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production

# 阶段 2:构建
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# 阶段 3:生产运行时(distroless)
FROM gcr.io/distroless/nodejs22-debian12 AS runtime
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package.json .
USER nonroot
EXPOSE 3000
CMD ["dist/server.js"]

Go 示例(scratch 镜像)

FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -ldflags="-w -s" -o server ./cmd/server

FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /app/server /server
USER 65534:65534
EXPOSE 8080
ENTRYPOINT ["/server"]

生成的镜像大小约为 10 MB,而不是 300+ MB。

Python 示例

FROM python:3.12-slim AS builder
WORKDIR /app
RUN pip install --upgrade pip
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt

FROM python:3.12-slim AS runtime
WORKDIR /app
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir --no-index --find-links /wheels /wheels/*.whl \
    && rm -rf /wheels
COPY src/ ./src/
RUN useradd --uid 1001 --create-home appuser
USER appuser
EXPOSE 8000
CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]

以非 root 用户运行

默认情况下,Docker 容器以 root 用户运行。如果攻击者逃逸出容器,他们将拥有主机的 root 权限。

# Debian/Ubuntu
RUN groupadd --gid 1001 appgroup \
    && useradd --uid 1001 --gid appgroup --shell /bin/sh --create-home appuser
USER appuser

# Alpine
RUN addgroup -S -g 1001 appgroup \
    && adduser -S -u 1001 -G appgroup -h /app appuser
USER appuser

# 数字 UID(适用于 distroless)
USER 1001:1001

Docker 镜像安全:多阶段构建、非 root 用户、镜像扫描与 Dockerfile 最佳实践 示意图

Kubernetes 安全上下文

securityContext:
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  runAsNonRoot: true
  runAsUser: 1001
  capabilities:
    drop:
      - ALL

Dockerfile 安全加固

固定精确版本

# 错误:使用浮动标签
FROM node:latest

# 正确:固定摘要以实现完全可重现
FROM node:22-alpine@sha256:abc123...

# 或至少固定次版本
FROM node:22.4.0-alpine3.19

避免在层中泄露密钥

# 错误:密钥被写入镜像层
RUN curl -H "Authorization: Bearer ${API_TOKEN}" https://api.example.com/download

# 正确:使用 BuildKit 密钥(从不写入层)
# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=api_token \
    curl -H "Authorization: Bearer $(cat /run/secrets/api_token)" \
    https://api.example.com/download

使用 .dockerignore

.git
.env
*.env
node_modules
__pycache__
*.pyc
.pytest_cache
coverage/
dist/
secrets/
*.key
*.pem

Docker 镜像安全:多阶段构建、非 root 用户、镜像扫描与 Dockerfile 最佳实践 示意图

COPY 与 ADD

# 错误:ADD 可以获取远程 URL 并自动解压归档
ADD https://example.com/package.tar.gz /tmp/

# 正确:COPY 仅复制本地文件
COPY package.tar.gz /tmp/
RUN tar xzf /tmp/package.tar.gz -C /app && rm /tmp/package.tar.gz

验证下载

RUN curl -fsSL https://example.com/tool-v1.2.3-linux-amd64.tar.gz \
    -o /tmp/tool.tar.gz \
    && echo "abc123expectedhash  /tmp/tool.tar.gz" | sha256sum -c - \
    && tar xzf /tmp/tool.tar.gz -C /usr/local/bin/ \
    && rm /tmp/tool.tar.gz

使用 Trivy 进行镜像扫描

# 扫描本地镜像
trivy image my-api:latest

# 如果发现 HIGH 或 CRITICAL 级别的 CVE,则 CI 失败
trivy image --exit-code 1 --severity HIGH,CRITICAL my-api:latest

# 在构建前扫描文件系统
trivy fs --security-checks vuln,config,secret .

在 GitHub Actions 中集成 Trivy

- name: Build image
  run: docker build -t my-api:${{ github.sha }} .

- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: my-api:${{ github.sha }}
    format: 'sarif'
    output: 'trivy-results.sarif'
    severity: 'CRITICAL,HIGH'
    exit-code: '1'

- name: Upload to GitHub Security tab
  uses: github/codeql-action/upload-sarif@v3
  if: always()
  with:
    sarif_file: 'trivy-results.sarif'

内容信任与签名

# 使用 cosign (Sigstore) 签名镜像
cosign sign --key cosign.key registry.example.com/my-api:v1.0.0

# 验证签名
cosign verify --key cosign.pub registry.example.com/my-api:v1.0.0

# 在 GitHub Actions 中无密钥签名
cosign sign registry.example.com/my-api:${{ github.sha }}

层优化

# 错误:分开的 RUN 命令创建额外层
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*

# 正确:合并相关命令
RUN apt-get update \
    && apt-get install -y --no-install-recommends curl ca-certificates \
    && rm -rf /var/lib/apt/lists/*

结论

Docker 镜像安全是一个分层学科。多阶段构建在大多数情况下将攻击面减少 80-90%。非 root 容器防止权限提升。CI 中的镜像扫描在已知 CVE 到达生产环境之前捕获它们。Dockerfile 最佳实践消除了常见陷阱,如泄露的密钥和未固定的依赖。系统地实施这些实践,您的容器将从第一天起达到生产级标准。