最可靠的方式是通过 runtimeArgs 在容器运行时层面锁定 rootfs 只读,即配置 daemon.json 启用 "--no-pivot" 和 "--no-new-privs",从引擎层禁用 pivot_root 与提权,比 --read-only 更彻底。

直接在容器启动时强制根文件系统为只读,最可靠的方式不是靠挂载卷或镜像层控制,而是通过 runtime 参数(runtimeArgs) 在容器运行时层面锁定整个 rootfs 的可写性。这比仅用 --read-only 命令行参数更底层、更不可绕过——尤其在 Docker 27+ 和 runc v1.2+ 环境下,它已深度集成进默认安全策略。
核心机制:--no-pivot + --no-new-privs 是关键组合
Docker 本身不直接暴露 --no-pivot 这类底层 runc 参数,但可通过 daemon 级配置注入。它的作用是禁用 pivot_root 系统调用,从而阻止容器在启动后切换根目录或重新挂载文件系统,配合 --no-new-privs(禁止提权),能真正封死 rootfs 被动态改为可写的路径。
-
--no-pivot:防止容器内进程执行pivot_root或chroot类操作,避免绕过只读限制 -
--no-new-privs:确保进程及其子进程无法通过 setuid/setgid 或 file capabilities 获取额外权限,切断提权改写 rootfs 的可能 - 二者协同,比单纯
--read-only更彻底——后者仅挂载时设为 ro,而前者从运行时引擎层掐断所有修改 rootfs 的能力链
配置方式:修改 /etc/docker/daemon.json
这不是容器启动命令里的临时参数,而是守护进程级的默认行为。将以下内容写入 /etc/docker/daemon.json:
{
"default-runtime": "runc",
"runtimes": {
"runc": {
"path": "runc",
"runtimeArgs": [
"--no-pivot",
"--no-new-privs"
]
}
}
}
保存后执行:
sudo systemctl restart docker
此后所有新启动的容器(除非显式覆盖 runtime)都会自动启用该加固模式。注意:--no-pivot 要求宿主机内核支持(Linux ≥ 3.18),且基础镜像需兼容(如 Alpine、Distroless 均适配)。
验证是否生效
启动一个测试容器:
docker run -it --rm alpine sh -c 'mount | grep " / " '
若输出中包含 ro,relatime 且无 rw,说明根挂载已是只读;再检查是否真的无法提权:
docker run -it --rm --cap-add=SYS_ADMIN alpine sh -c 'touch /test && echo "write succeeded"'
预期返回 touch: /test: Read-only file system —— 即使加了高危 capability,也无法突破只读根文件系统。
补充建议:与其它安全选项协同使用
单独启用 --no-pivot 不够全面。推荐搭配以下配置形成纵深防御:
-
--read-only:仍保留在docker run命令中,作为显式声明和兼容性保障 -
--security-opt no-new-privileges:与 daemon 配置中的--no-new-privs形成双重确认 -
--cap-drop=ALL --cap-add=NET_BIND_SERVICE:按需最小化 capability,避免因能力残留导致逃逸 - 使用 Distroless 或
nonroot基础镜像:从源头移除 shell 和包管理器,进一步压缩攻击面











