Docker容器安全加固实战:非root运行、权限最小化与镜像扫描完整指南

越来越多站长和运维用 Docker 部署业务,但很多人有个误区:“容器隔离 = 安全”。实际上容器共享宿主内核,一旦容器内进程拿到过高权限或暴露了 Docker 接口,攻击者完全可能逃逸到宿主机。近两年公开的容器逃逸漏洞(如 CVE-2022-0492、CVE-2024-21626)无一不在提醒:容器默认配置远不够安全。这篇教程按”运行时 → 权限 → 资源 → 镜像”四个维度,给出可直接复制的加固方案。

一、先认清风险:Docker 默认配置有哪些坑?

  • root 运行:容器内默认以 root 执行,逃逸后就是宿主 root;
  • 权限过大:默认 capabilities 较多(如 CAP_SYS_ADMIN),是逃逸的核心帮凶;
  • 共享挂载:把宿主目录直接挂进容器,等于把文件系统递给攻击者;
  • 资源无上限:单容器可耗尽 CPU/内存/进程数,拖垮整台宿主机;
  • 镜像投毒:从不可信源拉镜像,装上一运行就是后门。

下面每一项加固都对应解决上面至少一个问题。

二、运行时加固:非 root 运行 + 只读文件系统

第一件事:容器内不要用 root。在 Dockerfile 里显式声明运行用户,并给容器创建专用用户:

# Dockerfile 示例
FROM nginx:1.27-alpine
RUN addgroup -S app && adduser -S app -G app     && chown -R app:app /usr/share/nginx/html
USER app   # 关键:之后的所有进程都以 app 运行

# 构建时指定用户(不修改 Dockerfile 时的临时方案)
docker build --build-arg USER_ID=10001 -t myapp:v1 .
docker run --user 10001:10001 myapp:v1

再加只读文件系统:运行中的容器文件系统设为只读,应用只能写挂载出来的数据目录,攻击者即使进到容器里也写不进任何文件:

docker run -d --read-only   --tmpfs /tmp --tmpfs /run   -v /data/appdata:/data   myapp:v1

注意:--read-only 后容器内 /tmp、/run 等需要写入的目录要单独用 --tmpfs 或数据卷放行,否则应用启动会报 Permission denied。

三、权限最小化:capabilities + seccomp 双保险

Linux capabilities 决定进程能调用哪些特权操作。默认容器带一长串能力,加固思路是全部丢弃、按需添加

# 丢弃所有能力,只保留监听 80/443 端口所需的最小能力
docker run -d --cap-drop=ALL --cap-add=NET_BIND_SERVICE   --security-opt no-new-privileges   -p 8080:80 myapp:v1

# 查看容器实际拥有能力(应只剩 NET_BIND_SERVICE)
docker inspect --format '{{json .HostConfig.CapAdd}} {{json .HostConfig.CapDrop}}' 容器名

# 关键:永远不要给容器 --privileged 或 CAP_SYS_ADMIN,这是逃逸漏洞的主要入口

再加 seccomp 限制系统调用。Docker 默认的 seccomp 配置已禁用 44 个高危系统调用,显式指定更稳妥:

# 使用 Docker 官方默认 seccomp 配置
docker run -d --security-opt seccomp=/path/to/default.json myapp:v1

# 如果业务完全不需要特权类调用,可用更严格的 profile(只允许常规 syscall)
# 可从这里获取:https://github.com/docker/docker/tree/master/profiles/seccomp

--security-opt no-new-privileges 这条一定要加:禁止容器内进程通过 setuid 程序提权,属于成本为零的高收益加固项。

加固完怎么验证?三个命令:docker exec -it 容器 whoami 确认运行用户不是 root;docker inspect 查看 CapAdd/CapDrop、SecurityOpt 是否按预期生效;再尝试在容器内往非 tmpfs 目录写文件(read-only 模式下应报 Permission denied)。也可以直接 docker run --rm --security-opt no-new-privileges --cap-drop=ALL --user 10001:10001 myapp:v1 id 快速验证用户与权限是否符合预期。

四、资源限制与网络隔离:防止容器拖垮宿主机

# 限制 CPU、内存、进程数,杜绝容器失控
docker run -d --memory=512m --cpus=0.5   --pids-limit=100   --ulimit nofile=1024:2048   myapp:v1

# 网络:不要直接用默认 bridge 加 -p 映射
# 正确做法:建自定义网络,只对需要对外暴露的容器做端口映射
docker network create --driver bridge app-net
docker run -d --network app-net --network-alias web myapp:v1
# 仅入口容器映射端口,数据库等后端只在内网通信,不映射到宿主机
docker run -d --network app-net --name db -p 127.0.0.1:3306:3306 mysql:8

三个要点:一是端口映射尽量绑 127.0.0.1(如 -p 127.0.0.1:3306:3306),数据库、Redis 这类服务别裸奔到公网;二是内网容器间用自定义网络名通信,不依赖端口映射;三是 --internal 参数可以创建完全无外网访问的网络,适合跑任务型容器。

五、镜像安全:从源头掐断投毒

# 1) 安装 Trivy 做镜像漏洞扫描(支持本地镜像和 Dockerfile)
trivy image --severity HIGH,CRITICAL myapp:v1

# 2) 用 Docker Scout 检查供应链依赖
docker scout cves myapp:v1

# 3) 生产环境注意
# 只从官方仓库或可信私有仓库拉镜像
# 镜像 tag 必须固定(用 digest 更严格),禁止用 latest
# 多阶段构建减小体积、少装包 = 攻击面小
# 定期 docker pull 更新基础镜像并重建容器

镜像扫描不是走过场:高危基础镜像(如带已知 RCE 漏洞的旧版中间件)被拖进生产环境,等于自带后门上线。建议把”扫描结果高危=不允许部署”写进发布流程。

六、落地检查清单

检查项 要求
运行用户 禁止 root,必须 USER 指定普通用户
能力 –cap-drop=ALL,按需添加,禁 –privileged
提权防护 –security-opt no-new-privileges
文件系统 –read-only + –tmpfs 处理临时目录
资源限制 –memory / –cpus / –pids-limit 必须配置
端口暴露 只映射必要端口,内部服务绑 127.0.0.1
镜像来源 官方/私有可信仓库,固定 tag,部署前扫描
Docker 守护进程 docker.sock 严禁挂载进容器,2375 端口不对外

最后补一句:docker.sock 和 2375 端口是通往宿主机 root 的直通车,任何容器都不要挂载 /var/run/docker.sock,对外也绝不开 2375。

已经在跑的容器怎么迁移?不用改业务代码,只需把加固参数补进 docker run 命令重建一次即可,数据都在数据卷里,重建不丢:先用 docker inspect 导出原容器的挂载、网络、端口参数,再加上本文的加固选项重新 run。建议先在测试环境演练一遍,再把命令固化到 docker-compose.yml 或编排文件里,以后所有服务统一走这套配置,避免每次上线都靠手敲参数。

总结

Docker 安全加固没有一步到位的魔法,核心就四件事:普通用户跑、权限全收走、资源设上限、镜像先扫描。把这份清单逐项过一遍,你的容器就从”裸奔”变成”可上线”状态。

加固之外还有一层现实问题:即使容器配置再严谨,宿主机本身的网络层威胁(DDoS、CC、暴力扫描)依然防不胜防。如果业务有抗攻击需求,可以配合速度网络的高防服务器 / 高防 IP 使用:攻击流量在机房边缘清洗,宿主机只接收干净回源,容器集群稳定性更有保障。需要免费安全评估或加固方案咨询,欢迎联系主机吧(速度网络官方合作伙伴)。

主机吧 | 专注网络安全实战 · 速度网络官方合作伙伴,提供高防IP、高防CDN、WAF接入、高防服务器、SSL证书一站式服务。

给TA打赏
共{{data.count}}人
人已打赏
0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
在线客服
在线客服
热线电话
QQ客服
电子邮箱
suduwangluo