Docker容器安全完全指南:镜像扫描、运行时防护与最小权限配置实战

Docker容器的便利性有时以牺牲安全性为代价——默认配置下容器以root运行,可访问宿主机的大量系统资源。本文给出从镜像构建到运行时的全链路容器安全加固方案。

一、容器安全的五个层面

<code">Layer 1: 宿主机安全
  ↓ Linux内核漏洞修复、Docker daemon安全配置
Layer 2: Docker守护进程安全
  ↓ TLS认证、daemon.json加固
Layer 3: 容器镜像安全
  ↓ 最小基础镜像、漏洞扫描、多阶段构建
Layer 4: 容器运行时安全
  ↓ 非Root运行、只读文件系统、资源限制
Layer 5: 网络与Secrets安全
  ↓ 网络隔离、Secrets管理、不在环境变量中存密码

二、安全的Dockerfile构建

<code"># ❌ 不安全的写法
FROM ubuntu:latest
RUN apt update && apt install -y python3 python3-pip
COPY . /app
RUN pip install -r /app/requirements.txt
CMD ["python3", "/app/server.py"]
# 问题:以root运行、镜像体积大、未固定版本
<code"># ✅ 安全的多阶段构建
# 阶段1:构建阶段(含完整工具链)
FROM python:3.11-slim AS builder

WORKDIR /build
COPY requirements.txt .

# 安装依赖到指定目录(不污染系统)
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt


# 阶段2:运行阶段(最小镜像)
FROM python:3.11-slim AS runtime

# 创建专用非特权用户
RUN groupadd --gid 1001 appgroup && \
    useradd --uid 1001 --gid appgroup --shell /bin/sh --no-create-home appuser

WORKDIR /app

# 只复制必要文件(不复制.git、测试文件、开发配置)
COPY --from=builder /install /usr/local
COPY --chown=appuser:appgroup src/ ./src/

# 切换到非特权用户
USER appuser

# 明确声明容器监听端口
EXPOSE 8000

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

# 使用JSON格式(避免Shell包裹带来的信号处理问题)
CMD ["python3", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]

三、Trivy镜像漏洞扫描

<code"># 安装Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

# 扫描镜像(扫描已知CVE漏洞)
trivy image your-image:latest

# 只显示高危和严重漏洞
trivy image --severity HIGH,CRITICAL your-image:latest

# 以JSON格式输出(用于CI/CD集成)
trivy image --format json --output trivy-report.json your-image:latest

# 扫描本地文件系统中的Dockerfile和配置文件
trivy fs .

# 在GitHub Actions中集成(CI/CD安全门禁)
<code"># .github/workflows/security-scan.yml
name: Container Security Scan

on:
  push:
    branches: [main]

jobs:
  trivy-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build Docker image
        run: docker build -t myapp:${{ github.sha }} .

      - name: Trivy vulnerability scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
          severity: CRITICAL,HIGH
          exit-code: '1'      # 发现严重漏洞时CI失败

      - name: Upload scan results
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-results.sarif

四、容器运行时安全加固

docker-compose.yml安全配置

<code"># 生产级安全配置示例
version: '3.8'

services:
  app:
    image: your-secure-image:latest
    restart: unless-stopped

    # ======== 用户权限限制 ========
    user: "1001:1001"              # 以非root用户运行

    # ======== 文件系统限制 ========
    read_only: true                # 只读根文件系统
    tmpfs:
      - /tmp:size=100m,mode=1777  # 允许写入/tmp(内存中,重启清空)
      - /var/run:size=10m

    # ======== Linux Capabilities限制 ========
    cap_drop:
      - ALL                        # 先删除所有capabilities
    cap_add:
      - NET_BIND_SERVICE           # 只加回绑定低端口的能力(如需要)

    # ======== Seccomp安全计算模式 ========
    security_opt:
      - no-new-privileges:true     # 禁止容器进程提升权限
      - seccomp:./seccomp-profile.json  # 自定义系统调用白名单

    # ======== 资源限制 ========
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          memory: 256M

    # ======== 网络隔离 ========
    networks:
      - frontend        # 只连接必要的网络

    # ======== 健康检查 ========
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

networks:
  frontend:
    driver: bridge

五、Docker daemon安全配置

<code"># /etc/docker/daemon.json
{
    "icc": false,                          # 禁止容器间直接通信(通过网络策略控制)
    "no-new-privileges": true,             # 全局禁止权限提升
    "live-restore": true,                  # 守护进程重启时容器继续运行
    "userland-proxy": false,               # 禁用用户空间代理(安全漏洞来源)
    "log-driver": "json-file",            # 限制日志大小防止磁盘写满
    "log-opts": {
        "max-size": "50m",
        "max-file": "5"
    },
    "storage-driver": "overlay2",
    "selinux-enabled": false,              # CentOS环境改为true
    "default-ulimits": {
        "nofile": {"Name": "nofile", "Hard": 64000, "Soft": 64000}
    }
}
<code"># 重启Docker使配置生效
systemctl restart docker

# 验证配置
docker info | grep -E "Security|Logging|Storage"

六、Secrets管理(不在镜像和环境变量中存密钥)

<code"># 错误做法:密钥写在Dockerfile或环境变量中
ENV DB_PASSWORD=mysecretpassword  # ❌ 写入镜像层,docker history可见

# 正确做法1:Docker Secrets(Swarm模式)
echo "my-secret-password" | docker secret create db_password -

# 正确做法2:挂载外部密钥文件
secrets:
  db_password:
    file: ./secrets/db_password.txt  # 文件不提交Git

# 正确做法3:使用外部Secrets管理服务(HashiCorp Vault)
# 应用启动时从Vault获取密钥,而非从环境变量读取

七、容器网络安全

<code"># 为不同服务创建隔离的网络
docker network create --driver bridge \
    --opt com.docker.network.bridge.enable_icc=false \
    frontend_net

docker network create --driver bridge \
    --internal \    # 内部网络,无法访问外网
    backend_net

# 只有需要相互通信的容器才加入同一网络
# Web → frontend_net + backend_net
# DB  → backend_net(仅内部)
# 攻击者即使控制了Web容器,也无法直接访问外网

八、容器安全扫描定期执行

<code">#!/bin/bash
# /usr/local/bin/container-security-audit.sh
# 每周运行一次

echo "=== Docker容器安全审计 $(date) ==="

# 1. 扫描所有运行中的容器镜像
echo "--- 镜像漏洞扫描 ---"
for image in $(docker ps --format "{{.Image}}" | sort -u); do
    echo "扫描:$image"
    trivy image --severity CRITICAL --quiet "$image" 2>/dev/null
done

# 2. 检查以root运行的容器
echo -e "\n--- Root用户运行的容器(不安全) ---"
docker ps -q | while read id; do
    user=$(docker exec "$id" whoami 2>/dev/null)
    name=$(docker inspect --format '{{.Name}}' "$id")
    if [ "$user" = "root" ]; then
        echo "⚠️  $name 以root运行"
    fi
done

# 3. 检查暴露了不必要端口的容器
echo -e "\n--- 容器端口暴露情况 ---"
docker ps --format "table {{.Names}}\t{{.Ports}}"

# 4. 检查挂载了宿主机敏感目录的容器
echo -e "\n--- 挂载了宿主机目录的容器 ---"
docker ps -q | while read id; do
    name=$(docker inspect --format '{{.Name}}' "$id")
    mounts=$(docker inspect "$id" --format '{{range .Mounts}}{{.Source}}:{{.Destination}} {{end}}')
    if echo "$mounts" | grep -qE "(/etc|/proc|/sys|/var/run/docker.sock)"; then
        echo "⚠️  $name 挂载了敏感目录:$mounts"
    fi
done

九、总结

容器安全的核心原则:最小权限(只给容器运行所需的最小权限集)、最小攻击面(使用最小基础镜像)、不可变基础设施(容器无状态,数据外挂卷)。定期扫描镜像漏洞,配合CI/CD安全门禁,可以在漏洞修复发布后快速更新。IDC.Net的香港VPSKVM架构完整支持所有Docker安全特性,包括Seccomp、AppArmor和用户命名空间隔离。

THE END