核心方法是在构建镜像时显式创建非root用户并提前切换,确保CMD/ENTRYPOINT以该用户执行;需用adduser/useradd创建UID≥1001用户、COPY--chown赋权、USER指令置于COPY后CMD前,并清理SUID/SGID文件、禁用提权工具。
核心方法是在构建镜像时显式创建非 root 用户,并在运行前完成用户切换和文件权限归属设置,确保容器启动后所有进程(包括 cmd 和 entrypoint)都以该用户身份执行,不经过任何 root 权限上下文。
在 Dockerfile 中固定用户并提前切换
不要依赖运行时传参或后期切换——必须在构建阶段就完成用户创建、目录归属、文件属主设定和 USER 指令声明。典型写法如下:
- 使用 RUN adduser -u 1001 -D appuser 或 useradd -r -u 1001 -g appuser appuser 创建 UID 确定的非特权用户(推荐 UID ≥ 1001,避开宿主机常用用户)
- 用 COPY --chown=appuser:appuser 复制应用代码,避免文件属主仍为 root
- 对工作目录、缓存路径等关键位置执行 RUN chown -R appuser:appuser /app /home/appuser
- USER appuser 必须出现在 COPY 之后、CMD 之前,且不能被后续 RUN 指令覆盖
禁用所有隐式提权路径
即使指定了 USER,某些操作仍可能意外触发 root 权限行为。需主动拦截:
使用 Python 脚本下载 Docker 镜像并打包为 tar 文件。用户说“下载镜像”“拉取镜像”“pull xxx”“帮我下载 nginx 镜像”等时触发。支持 SOCKS5 代理直连(推荐)和国内镜像站加速两种模式。
- 删除镜像中所有 SUID/SGID 文件:RUN find / -type f \( -perm -4000 -o -perm -2000 \) -exec rm -f {} \; 2>/dev/null
- 禁止容器内安装或调用 sudo、su、passwd 等提权工具(多阶段构建时不在最终镜像中保留)
- 避免在 CMD 中使用 sh -c "whoami && exec ./app" 类包装——sh 默认以当前 USER 启动,但若脚本内含 su 或 setuid 调用,风险仍在;应直接 CMD ["./app"]
运行时加固:堵住最后的逃逸缝隙
构建层控制之外,运行时参数可提供第二道防线:
- 强制指定用户:docker run --user 1001:1001,覆盖 Dockerfile 中的 USER(适用于调试或灰度验证)
- 启用 --read-only 根文件系统,配合 --tmpfs /tmp 提供必要可写空间
- 添加 --security-opt no-new-privileges,阻止进程通过 execve 获得新权限
- 挂载宿主机目录时明确指定 uid/gid:-v $(pwd)/data:/app/data:Z(SELinux)或 -v $(pwd)/data:/app/data --user 1001:1001
验证是否真正脱离 root
启动容器后立即检查,不能只看 Dockerfile 是否写了 USER:
- 执行 docker exec -it <container> ps aux | head -3,确认 PID 1 进程的 USER 列显示为 appuser 或 UID 1001,而非 root 或 0
- 进入容器运行 id 和 cat /proc/1/status | grep Uid,检查 Real、Effective、Saved UID 全部为非 0 值
- 尝试 touch /tmp/test && ls -l /tmp/test,确认属主是目标用户,且无权限报错

















