容器默认以 root 运行、文件系统可写、镜像可能含漏洞——这三个问题让 Docker 成为攻击者的温床。本文提供一份可直接落地的安全加固清单,覆盖镜像、容器、运行时三层。
一、容器安全的三个层面
┌─────────────────────────────────────┐ │ 应用层安全 │ │ (代码漏洞、依赖漏洞、配置错误) │ ├─────────────────────────────────────┤ │ 镜像层安全 │ │ (基础镜像、构建过程、敏感信息) │ ─────────────────────────────────────┤ │ 运行时安全 │ │ (权限、网络、资源限制) │ ─────────────────────────────────────┘二、镜像层安全
2.1 使用官方/可信基础镜像
# 推荐:官方镜像,定期更新 FROM node:18-alpine # 不推荐:来路不明的镜像 FROM someuser/custom-node:latest # 更推荐:固定版本 + SHA256 FROM node@sha256:abc123...2.2 最小化镜像
# 使用 Alpine 或 distroless FROM node:18-alpine # ~180MB # 而不是 FROM node:18 # ~1GB # 极致:distroless(无 shell、无包管理器) FROM gcr.io/distroless/nodejs18distroless 的优势:
- 没有 shell,攻击者无法 docker exec 进入
- 没有包管理器,无法安装恶意工具
- 镜像体积小,攻击面小
2.3 多阶段构建减少攻击面
# 阶段一:构建 FROM golang:1.21 AS builder WORKDIR /app COPY . . RUN CGO_ENABLED=0 go build -o server . # 阶段二:运行(只包含二进制文件) FROM alpine:3.18 RUN adduser -D appuser USER appuser COPY --from=builder /app/server /usr/local/bin/ CMD ["server"]2.4 不要在镜像中硬编码敏感信息
# 错误:密码写在 Dockerfile 里 ENV DB_PASSWORD=supersecret123 # 正确:运行时通过环境变量或 Secret 传入 # docker run -e DB_PASSWORD=$DB_PASSWORD myapp# 检查镜像中的敏感信息 docker history myapp:latest docker inspect myapp:latest | grep -i password2.5 .dockerignore 排除敏感文件
.git .env *.key *.pem secrets/ credentials.json
三、容器运行时安全
3.1 非 root 运行
为什么重要:容器内 root 虽然不等于宿主机 root,但仍有权限逃逸风险(如 CVE-2019-5736)。
# Dockerfile 中指定非 root 用户 FROM node:18-alpine # 创建用户 RUN addgroup -S appgroup && adduser -S appuser -G appgroup # 切换到非 root 用户 USER appuser WORKDIR /home/appuser/app COPY --chown=appuser:appgroup . . CMD ["node", "index.js"]# docker run 时指定 docker run --user 1000:1000 myapp # docker-compose.yml services: app: image: myapp user: "1000:1000"3.2 只读文件系统
# 容器文件系统只读,需要写的地方用 tmpfs 或 volume docker run --read-only \ --tmpfs /tmp \ --tmpfs /var/run \ myapp# docker-compose.yml services: app: image: myapp read_only: true tmpfs: - /tmp - /var/run3.3 限制 Linux Capabilities
容器默认有 14 个 Linux Capabilities,大多数应用只需要少数几个。
# 删除所有 capabilities,只保留需要的 docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp # 常见场景: # - Web 服务绑定 80/443 端口:NET_BIND_SERVICE # - 需要 ping:NET_RAW # - 需要修改系统时间:SYS_TIME# docker-compose.yml services: app: image: myapp cap_drop: - ALL cap_add: - NET_BIND_SERVICE3.4 禁止特权模式
# 绝对不要在生产环境使用 docker run --privileged myapp # 危险! # 如果确实需要特定权限,用 --cap-add 精确控制 docker run --cap-add=SYS_PTRACE myapp # 调试用3.5 限制资源
# 限制 CPU 和内存 docker run --cpus=2 --memory=512m myapp # 限制 PID 数量(防 fork 炸弹) docker run --pids-limit=100 myapp# docker-compose.yml services: app: image: myapp deploy: resources: limits: cpus: '2' memory: 512M reservations: cpus: '0.5' memory: 128M3.6 网络隔离
# 不暴露不必要的端口 docker run -p 8080:80 myapp # 只暴露 8080 # 使用自定义网络 docker network create isolated-net docker run --network isolated-net myapp# docker-compose.yml services: app: image: myapp ports: - "8080:80" networks: - frontend db: image: postgres ports: [] # 不暴露到宿主机 networks: - backend networks: frontend: backend:四、镜像漏洞扫描
4.1 Docker Scout(官方)
# 登录 Docker Hub docker scout login # 扫描本地镜像 docker scout cves myapp:latest # 扫描远程镜像 docker scout cves docker.io/library/nginx:latest # 生成报告 docker scout cves myapp:latest --format json > report.json输出示例:
✓ Image stored for indexing ✓ Indexed 142 packages ✗ 3 vulnerabilities found - 1 Critical - 2 High4.2 Trivy(Aqua Security)
# 安装 brew install trivy # macOS apt-get install trivy # Debian/Ubuntu # 扫描镜像 trivy image myapp:latest # 扫描文件系统 trivy fs /path/to/project # 只扫描高危及以上 trivy image --severity HIGH,CRITICAL myapp:latest # 输出为表格 trivy image --format table myapp:latest # 输出为 JSON(用于 CI/CD) trivy image --format json --output report.json myapp:latest4.3 Grype(Anchore)
# 安装 brew install grype # 扫描镜像 grype myapp:latest # 只扫描指定严重级别 grype --only-fixed myapp:latest4.4 CI/CD 集成示例
# GitHub Actions name: Security Scan on: push: branches: [main] jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build image run: docker build -t myapp:${{ github.sha }} . - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master with: image-ref: 'myapp:${{ github.sha }}' format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH' - name: Upload Trivy scan results uses: github/codeql-action/upload-sarif@v2 with: sarif_file: 'trivy-results.sarif'五、Docker 守护进程安全
5.1 启用 TLS 认证
# 生成 CA 证书 openssl genrsa -aes256 -out ca-key.pem 4096 openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem # 生成服务器证书 openssl genrsa -out server-key.pem 4096 openssl req -subj "/CN=$HOST" -sha256 -new -key server-key.pem -out server.csr openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem # 配置 Docker 守护进程 # /etc/docker/daemon.json { "tls": true, "tlscert": "/path/to/server-cert.pem", "tlskey": "/path/to/server-key.pem", "tlscacert": "/path/to/ca.pem" }5.2 限制 API 访问
# 不要直接暴露 Docker API 到公网 # 错误 dockerd -H tcp://0.0.0.0:2375 # 正确:使用 Unix socket 或 TLS dockerd -H unix:///var/run/docker.sock5.3 启用审计日志
# 配置 Docker 审计 # /etc/docker/daemon.json { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }六、安全加固检查清单
镜像构建阶段
- 使用官方/可信基础镜像
- 固定基础镜像版本(不用 latest)
- 多阶段构建,减少最终镜像大小
- 不在镜像中硬编码密码/密钥
- 使用 .dockerignore 排除敏感文件
- 定期扫描镜像漏洞(Trivy/Docker Scout)
容器运行阶段
- 非 root 用户运行
- 只读文件系统(--read-only)
- 删除不必要的 Linux Capabilities
- 不使用 --privileged
- 限制 CPU、内存、PID 数量
- 不暴露不必要的端口
- 使用自定义网络隔离
守护进程配置
- 启用 TLS 认证(远程访问时)
- 不暴露 Docker API 到公网
- 配置日志轮转
- 定期更新 Docker 版本
七、常见误区
误区 1:容器 = 虚拟机,很安全
事实:容器共享宿主机内核,权限逃逸风险真实存在。2019 年的 CVE-2019-5736 允许容器内 root 逃逸到宿主机。
误区 2:非 root 运行就没风险
事实:非 root 降低了风险,但不能完全消除。配合只读文件系统、capabilities 限制才能形成纵深防御。
误区 3:扫描没漏洞就安全
事实:漏洞扫描只检查已知 CVE。配置错误(如暴露敏感端口、硬编码密码)需要人工审查。
八、总结
Docker 安全不是单一措施,而是纵深防御:
镜像安全 → 运行时安全 → 网络隔离 → 监控审计
把本文的检查清单融入 CI/CD 流程,每次构建自动扫描漏洞,每次部署自动检查配置,才能让 Docker 真正安全地跑在生产环境。