Docker 安全实践
大约 11 分钟约 3413 字
Docker 安全实践
简介
Docker 容器的安全性是容器化部署中不可忽视的环节。不当的容器配置可能导致容器逃逸、资源耗尽、数据泄露等安全风险。通过镜像扫描、以非 root 用户运行、限制资源配额、网络隔离和密钥管理等安全实践,可以显著提升容器的安全水位。
特点
镜像安全扫描
使用 Trivy 扫描镜像漏洞
# 安装 Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh
# 扫描远程镜像
trivy image nginx:latest
# 扫描指定严重级别的漏洞
trivy image --severity HIGH,CRITICAL nginx:latest
# 输出 JSON 格式报告
trivy image --format json --output report.json nginx:latest
# 在 CI/CD 中使用(发现高危漏洞时退出码非零)
trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:v1.0
# 扫描本地镜像
docker save myapp:v1.0 -o myapp.tar
trivy image --input myapp.tarDockerfile 安全最佳实践
# 使用特定版本标签,避免 latest
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
# 创建非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# 设置工作目录
WORKDIR /app
# 仅复制必要文件
COPY --chown=appuser:appgroup publish/ .
# 切换到非 root 用户
USER appuser
# 声明端口
EXPOSE 8080
# 使用 ENTRYPOINT 而非 CMD(更安全)
ENTRYPOINT ["dotnet", "MyApp.dll"]# Node.js 安全 Dockerfile 示例
FROM node:20-alpine
RUN addgroup -g 1001 appgroup && \
adduser -u 1001 -G appgroup -s /bin/sh -D appuser
WORKDIR /app
COPY --chown=appuser:appgroup package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY --chown=appuser:appgroup . .
USER appuser
EXPOSE 3000
CMD ["node", "server.js"]镜像签名与信任
# 启用 Docker Content Trust
export DOCKER_CONTENT_TRUST=1
# 推送签名镜像
docker push myregistry/myapp:v1.0
# 查看镜像签名信息
docker trust inspect myregistry/myapp:v1.0
# 使用 Notary 管理签名
notary list myregistry/myapp
# 验证镜像摘要
docker pull myregistry/myapp@sha256:abc123...Rootless 容器
以非 root 用户运行容器
# 方法一:Dockerfile 中指定用户
# USER 1001:1001
# 方法二:运行时指定用户
docker run --user 1001:1001 nginx
# 方法三:使用 user namespace 映射
# /etc/docker/daemon.json
# {
# "userns-remap": "default"
# }
# 重启 Docker 使配置生效
systemctl restart docker
# 验证 user namespace 配置
docker info | grep "User Namespace"Docker Rootless 模式
# 安装 rootless 模式
curl -fsSL https://get.docker.com/rootless | sh
# 配置环境变量
export PATH=/home/deploy/bin:$PATH
export DOCKER_HOST=unix:///run/user/1000/docker.sock
# 启动 rootless Docker
systemctl --user start docker
# 验证运行模式
docker info | grep "Rootless"
# rootless 模式下运行容器
docker run -d -p 8080:80 nginxLinux Capabilities 限制
# 查看容器默认 capabilities
docker run --rm alpine capsh --print
# 运行时丢弃所有 capabilities
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx
# 常用 capabilities 说明
| Capability | 说明 |
|---|---|
| NET_BIND_SERVICE | 绑定 1024 以下端口 |
| SYS_PTRACE | 调试进程 |
| CHOWN | 修改文件所有者 |
| SETUID/SETGID | 切换用户/组身份 |
| DAC_OVERRIDE | 绕过文件权限检查 |
# 禁止容器获取新权限
docker run --security-opt no-new-privileges nginx
# 只读根文件系统
docker run --read-only --tmpfs /tmp --tmpfs /run nginx资源限制
运行时资源限制
# 限制 CPU 和内存
docker run -d \
--name myapp \
--cpus="1.5" \
--memory="512m" \
--memory-swap="1g" \
--memory-reservation="256m" \
--pids-limit 100 \
nginx
# 限制 CPU 核心(绑定到特定核心)
docker run -d \
--cpuset-cpus="0,1" \
--cpu-shares=512 \
nginx
# 更新运行中容器的资源限制
docker update --cpus="2.0" --memory="1g" myapp
# 查看容器资源使用
docker stats myapp
# 查看容器资源限制配置
docker inspect myapp --format '{{.HostConfig.Memory}}'Docker Compose 资源限制
# docker-compose.yml
version: '3.8'
services:
api:
image: myapp:v1.0
deploy:
resources:
limits:
cpus: '2.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 256M
ulimits:
nofile:
soft: 65536
hard: 65536
nproc:
soft: 4096
hard: 4096
pids_limit: 200
worker:
image: myapp-worker:v1.0
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M网络隔离
Docker 网络安全
# 创建自定义隔离网络
docker network create --driver bridge \
--subnet 172.20.0.0/16 \
--ip-range 172.20.0.0/24 \
internal-net
# 创建不允许外网访问的内部网络
docker network create --internal \
--driver bridge \
no-internet-net
# 运行容器到隔离网络
docker run -d --network internal-net --name api myapp
docker run -d --network no-internet-net --name db mysql
# 连接容器到多个网络
docker network connect internal-net db
# 查看网络详情
docker network inspect internal-net
# 禁止容器间通信(ICC)
# /etc/docker/daemon.json
# {
# "iptables": true,
# "icc": false
# }端口暴露安全
# 仅绑定到本地回环地址
docker run -d -p 127.0.0.1:8080:80 nginx
# 避免使用 --network host(共享宿主网络栈)
# docker run --network host nginx # 不推荐
# 使用 Docker Compose 内部端口# docker-compose.yml — 网络隔离示例
version: '3.8'
services:
frontend:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
networks:
- frontend-net
backend:
image: myapp:v1.0
ports:
- "127.0.0.1:8080:8080"
networks:
- frontend-net
- backend-net
database:
image: postgres:16-alpine
# 不暴露端口到宿主机
networks:
- backend-net
networks:
frontend-net:
driver: bridge
backend-net:
driver: bridge
internal: true密钥管理
使用 Docker Secrets(Swarm 模式)
# 创建 secret
echo "MySuperSecretPassword" | docker secret create db_password -
# 在服务中使用 secret
docker service create \
--name api \
--secret db_password \
-e DB_PASSWORD_FILE=/run/secrets/db_password \
myapp:v1.0
# 查看 secrets 列表
docker secret ls
# 删除 secret
docker secret rm db_password使用文件挂载管理密钥
# 创建密钥文件
echo "db_password_value" > /opt/secrets/db_password
chmod 400 /opt/secrets/db_password
# 挂载密钥文件到容器
docker run -d \
--name myapp \
--mount type=bind,source=/opt/secrets/db_password,target=/run/secrets/db_password,readonly \
-e DB_PASSWORD_FILE=/run/secrets/db_password \
myapp:v1.0
# 使用 tmpfs 挂载(仅保存在内存中)
docker run -d \
--mount type=tmpfs,destination=/run/secrets,tmpfs-size=1m,tmpfs-mode=0400 \
myapp:v1.0避免在镜像中泄露密钥
# 错误:密钥写在 Dockerfile 中
# ENV DB_PASSWORD=mysecret # 不要这样做!
# 正确:使用多阶段构建,构建参数不保留在最终镜像
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG NUGET_TOKEN
# 使用构建参数拉取私有包
RUN dotnet nuget add source https://nuget.example.com/v3/index.json \
-n private -u user -p ${NUGET_TOKEN} --store-password-in-clear-text
RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
# 构建参数不会出现在最终镜像中
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]# 构建时传入参数
docker build --build-arg NUGET_TOKEN=xxx -t myapp:v1.0 .
# 验证镜像中是否泄露密钥
docker history myapp:v1.0
docker inspect myapp:v1.0 | grep -i passwordSeccomp 与 AppArmor
安全计算模式
# Seccomp 限制容器可以调用的系统调用
# Docker 默认使用 Seccomp 配置文件,禁用了约 44 个危险系统调用
# 查看默认 Seccomp 配置
docker info | grep "Security Options"
# 使用自定义 Seccomp 配置
docker run --security-opt seccomp=custom-seccomp.json nginx
# 完全禁用 Seccomp(不推荐)
docker run --security-opt seccomp=unconfined nginx
# 常见被禁用的系统调用:
# - keyctl, add_key, request_key(密钥管理)
# - ptrace(进程调试)
# - mount, umount(文件系统挂载)
# - reboot(系统重启)
# - module_load, module_delete(内核模块)
# AppArmor — 基于路径的访问控制
# 查看当前 AppArmor 配置
sudo aa-status
# 使用 AppArmor 配置文件运行容器
docker run --security-opt apparmor=docker-default nginx
# 自定义 AppArmor 配置文件示例
# /etc/apparmor.d/docker-custom
# profile docker-custom flags=(attach_disconnected,mediate_deleted) {
# #include <abstractions/base>
# network inet tcp,
# network inet udp,
# deny /proc/sys/** w,
# deny /sys/** w,
# }容器运行时安全
日志审计与监控
# docker-compose.yml — 安全监控栈
version: '3.8'
services:
# Falco — 运行时安全监控
falco:
image: falcosecurity/falco:latest
privileged: true
volumes:
- /var/run/docker.sock:/host/var/run/docker.sock
- /dev:/host/dev
- /proc:/host/proc:ro
- /boot:/host/boot:ro
- /lib/modules:/host/lib/modules:ro
- /usr:/host/usr:ro
- /etc:/host/etc:ro
environment:
- FALCO_BPF_PROBE=""
# Docker Bench Security — 安全基线检查
bench:
image: docker/docker-bench-security
privileged: true
network_mode: host
pid: host
volumes:
- /etc:/etc:ro
- /lib/systemd/system:/lib/systemd/system:ro
- /usr/bin/docker:/usr/bin/docker:ro
- /usr/bin/containerd:/usr/bin/containerd:ro
- /var/lib/docker:/var/lib/docker:ro
- /var/run/docker.sock:/var/run/docker.sock:ro# 运行 Docker Bench Security 检查
docker run --rm --net host --pid host \
--privileged \
-v /etc:/etc:ro \
-v /lib/systemd/system:/lib/systemd/system:ro \
-v /usr/bin/docker:/usr/bin/docker:ro \
-v /var/lib/docker:/var/lib/docker:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
docker/docker-bench-security
# 审计 Docker 事件
docker events --filter 'type=container' --filter 'event=start'
# 查看 Docker 审计日志
journalctl -u docker.service --since "1 hour ago"
# 容器进程监控
docker top <container_id>
docker stats --no-stream镜像安全最佳实践清单
Dockerfile 安全检查项
# 1. 使用特定版本的基础镜像
FROM node:20.10-alpine # 好
# FROM node:latest # 差
# 2. 使用最小化基础镜像
FROM alpine:3.18 # 好之一
FROM distroless # 更好(无 shell,攻击面极小)
# FROM ubuntu:22.04 # 攻击面较大
# 3. 不安装不必要的包
RUN apk add --no-cache curl=8.4.0-r0 # 指定版本
# RUN apt-get install curl # 可能安装额外依赖
# 4. 多阶段构建减小镜像体积
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS runtime
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=build --chown=appuser:appgroup /app .
USER appuser
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApp.dll"]
# 5. 固定镜像摘要(防止供应链攻击)
# FROM mcr.microsoft.com/dotnet/aspnet:8.0@sha256:abc123...CI/CD 安全扫描集成
# GitLab CI 安全扫描示例
stages:
- build
- scan
- deploy
build:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
scan:
stage: scan
image: aquasec/trivy:latest
script:
# 镜像漏洞扫描
- trivy image --exit-code 1 --severity HIGH,CRITICAL $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
# 镜像配置扫描(Dockerfile 检查)
- trivy config --exit-code 1 .
# SBOM 生成
- trivy image --format spdx-json --output sbom.json $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
artifacts:
paths:
- sbom.json
deploy:
stage: deploy
script:
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main容器逃逸防护
常见逃逸途径与防护
# 容器逃逸是指攻击者从容器内部获取宿主机的控制权
# 常见途径和防护措施:
# 1. 特权容器(--privileged)
# 风险:容器可以访问所有设备、加载内核模块
# 防护:永远不要使用 --privileged
docker run -d nginx # 正确
# docker run --privileged -d nginx # 错误
# 2. 挂载 Docker Socket
# 风险:容器可以控制宿主机上的 Docker
# 防护:不要挂载 /var/run/docker.sock
# docker run -v /var/run/docker.sock:/var/run/docker.sock ... # 危险
# 3. 挂载宿主机关键目录
# 风险:容器可以修改宿主机系统文件
# 防护:不要挂载 /、/etc、/root 等敏感目录
# 使用只读挂载(:ro)如果必须挂载
# 4. 内核漏洞利用
# 防护:保持内核和 Docker 版本更新
# 使用 user namespace 隔离
# 启用 Seccomp 和 AppArmor
# 5. 安全加固 Docker 守护进程
# /etc/docker/daemon.json 安全配置
# {
# "userns-remap": "default",
# "icc": false,
# "live-restore": true,
# "userland-proxy": false,
# "no-new-privileges": true,
# "seccomp-profile": "/etc/docker/seccomp.json",
# "log-driver": "json-file",
# "log-opts": {"max-size": "10m", "max-file": "3"}
# }优点
缺点
总结
Docker 安全实践涵盖了从镜像构建到容器运行的完整生命周期。通过 Trivy 等工具进行镜像漏洞扫描,使用非 root 用户和 capabilities 限制实现最小权限,配置资源限制防止单容器资源耗尽,利用网络隔离缩小攻击面,以及通过 Secrets 或安全挂载管理敏感信息,可以构建起一套完整的容器安全体系。安全是一个持续的过程,需要定期扫描镜像、更新基础镜像、审查安全配置,以应对不断变化的安全威胁。
关键知识点
- DevOps 主题的核心是让交付更快、更稳、更可审计。
- 自动化不是把命令脚本化,而是把失败、回滚、权限和观测一起设计进去。
- 生产链路必须明确制品、环境、凭据、配置和责任边界。
- 部署主题通常要同时看镜像、容器、卷、网络和宿主机资源。
项目落地视角
- 把流水线拆成构建、测试、制品、部署、验证和回滚几个阶段。
- 为关键步骤补齐日志、指标、通知和人工兜底点。
- 定期演练扩容、回滚、故障注入和灾备切换。
- 固定镜像标签,记录端口、挂载目录、环境变量和自启动策略。
常见误区
- 只关注部署成功,不关注失败恢复和审计追踪。
- 把环境差异藏在临时脚本或人工操作里。
- 上线频率高了以后,没有标准化制品和配置管理。
- 使用 latest 导致结果不可复现。
进阶路线
- 继续补齐 GitOps、可观测性、平台工程和成本治理。
- 把主题和应用架构、安全、权限、备份恢复联动起来理解。
- 形成团队级平台能力,而不是每个项目重复造轮子。
- 继续补齐 Compose 编排、镜像瘦身、安全扫描和镜像仓库治理。
适用场景
- 当你准备把《Docker 安全实践》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合构建自动化交付、基础设施治理、监控告警和生产发布体系。
- 当团队规模扩大、发布频率提升或环境变多时,这类主题会显著影响交付效率。
落地建议
- 所有自动化流程尽量做到幂等、可审计、可回滚。
- 把制品、变量、凭据和执行权限分层管理。
- 定期演练扩容、回滚、密钥轮换和灾备恢复。
排错清单
- 先定位失败发生在代码、构建、制品、环境还是权限层。
- 检查流水线变量、凭据、镜像标签和目标环境配置是否一致。
- 如果问题偶发,重点看并发发布、资源争抢和外部依赖抖动。
复盘问题
- 如果把《Docker 安全实践》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《Docker 安全实践》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《Docker 安全实践》最大的收益和代价分别是什么?
