目录
Rootless Docker 与 Podman 教程:无需 root 运行容器
安全最佳实践 实测过:能使用 Docker 的人,就能把主机的根目录挂载到容器中,读写任何文件,相当于拥有主机的 root 权限。原因是 Docker Engine(dockerd)本身以 root 运行,容器里的 root 就是主机的 root(只是受到各种隔离机制的限制)。
本节介绍两种无需 root 的做法:Docker 的 Rootless 模式,以及另一个容器工具 Podman。
Rootless Docker:Docker Engine 以普通用户运行
Rootless 模式让 dockerd 和所有容器都以普通用户运行。它使用 Linux 的 user namespace:容器里看起来是 root(uid 0),在主机上实际上对应你的账号或一段非特权 uid。
本节使用官方的 docker:29.9-dind-rootless 镜像进行实测,不会影响主机配置:
$ docker run -d --name rl --privileged docker:29.9-dind-rootless
93e609c8808360d7e63bdc214ab2ff0e76af520fb0fb9dec5b5852d57a4881fe
$ docker logs rl 2>&1 | tail -n 5
time="2026-10-13T05:22:59.775914576Z" level=info msg="Initializing buildkit"
time="2026-10-13T05:22:59.918099929Z" level=info msg="Completed buildkit initialization"
time="2026-10-13T05:22:59.929895038Z" level=info msg="Daemon has completed initialization"
time="2026-10-13T05:22:59.930093633Z" level=info msg="API listen on /run/user/1000/docker.sock"
time="2026-10-13T05:22:59.930231753Z" level=info msg="API listen on [::]:2376"
$ docker exec rl id
uid=1000(rootless) gid=1000(rootless) groups=1000(rootless)
日志显示 Docker Engine 监听的是 /run/user/1000/docker.sock(用户 1000 专属的路径),而不是普通的 /var/run/docker.sock;运行 dockerd 的用户是 rootless(uid 1000),而不是 root。
容器里的 root 是谁
让 docker 命令连接到 rootless 的 socket(DOCKER_HOST),并运行一个容器:
$ docker exec -e DOCKER_HOST=unix:///run/user/1000/docker.sock rl docker info --format '{{json .SecurityOptions}}'
["name=seccomp,profile=builtin","name=rootless"]
$ docker exec -e DOCKER_HOST=unix:///run/user/1000/docker.sock rl docker run -d --name sleeper alpine:3.23 sleep 300
18986d442e9b9282178d96e7187534b9ac19a83dd42e3b4671a5243b55bcf1ff
$ docker exec -e DOCKER_HOST=unix:///run/user/1000/docker.sock rl docker exec sleeper id
uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
$ docker exec rl ps -o user,pid,args | grep 'sleep 300' | grep -v grep
rootless 382 sleep 300
docker info的安全选项中多了name=rootless。- 容器里的
id显示uid=0(root),对容器里的程序来说它就是 root。 - 从主机(外层)查看同一个
sleep 300进程,其所有者是普通用户rootless。
实测:挂载主机根目录也读不到 /etc/shadow
/etc/shadow 存放密码哈希,只有 root 能读取。分别使用普通 Docker 和 Rootless Docker 挂载主机的 / 并读取它:
$ docker run --rm -v /:/host alpine:3.23 sh -c "id -u; head -n 1 /host/etc/shadow"
0
root:*::0:::::
$ docker exec -e DOCKER_HOST=unix:///run/user/1000/docker.sock rl docker run --rm -v /:/host alpine:3.23 sh -c 'id -u; head -n 1 /host/etc/shadow'
0
head: /host/etc/shadow: Permission denied
- 普通 Docker:容器内的 root 直接读到了主机的
/etc/shadow。 - Rootless Docker:容器内同样是 root(
id -u为 0),但读取时得到Permission denied。因为它在主机上只是普通用户,权限不会高于启动 Docker 的那个账号。
因此,即使容器遭到入侵,或有人误用了 -v /:/host,影响范围也仅限于那个用户账号。
在 Linux 上安装 Rootless Docker
以下步骤根据 Docker 官方文档整理。本教程没有在主机上实际安装(安装会修改主机的系统配置),实际操作时请以官方文档为准。
# 1. 安装需要的软件包(Ubuntu / Debian)
sudo apt-get install -y uidmap dbus-user-session
# 2. 如果已经安装普通 Docker,建议先停用
sudo systemctl disable --now docker.service docker.socket
# 3. 以普通用户运行安装脚本(随 Docker Engine 软件包安装)
dockerd-rootless-setuptool.sh install
# 4. 让 docker 命令连接到 rootless 的 socket(添加到 ~/.bashrc)
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
# 5. 让它在开机时启动(用户登出后也继续运行)
systemctl --user enable docker
sudo loginctl enable-linger $(whoami)
Rootless 的限制
| 限制 | 说明与解决方法 |
|---|---|
| 1024 以下的端口 | 普通用户默认不能绑定 80、443,-p 80:80 会失败。改用 8080 等端口,再由反向代理转发;或者调整系统配置 net.ipv4.ip_unprivileged_port_start |
| 资源限制 | --memory、--cpus 需要主机使用 cgroup v2,并由 systemd 向用户委派权限 |
| 网络性能 | 通过用户空间的网络组件转发,吞吐量低于普通模式 |
| 数据位置 | 镜像和 Volume 存在用户的主目录(~/.local/share/docker),与普通模式的数据不共享 |
| 部分功能 | 例如 AppArmor 配置、部分存储和网络驱动程序不受支持 |
本教程的测试环境调整过
ip_unprivileged_port_start,因此-p 80:80没有报错,上表第一点无法在这里重现。
Podman:无需常驻进程的容器工具
Podman 是由 Red Hat 主导的开源容器工具,在 Fedora、RHEL 等发行版中是默认的容器工具。它和 Docker 最大的区别是:
- 没有常驻进程(daemonless):Docker 的命令要通过 dockerd 运行;Podman 的命令直接创建容器,容器是你这个进程的子进程。
- 默认采用 rootless:普通用户运行
podman,就是 rootless 模式。 - 命令几乎与 Docker 相同:
run、build、ps、images、exec、logs等参数都一样。
实测:像使用 Docker 一样使用 Podman
在 quay.io/podman/stable 镜像中运行(测试主机的 cgroup v1 会让 Podman 显示弃用警告,因此添加 PODMAN_IGNORE_CGROUPSV1_WARNING=1 将其隐藏):
$ docker exec -e PODMAN_IGNORE_CGROUPSV1_WARNING=1 podman podman --version
podman version 5.8.7
$ docker exec -e PODMAN_IGNORE_CGROUPSV1_WARNING=1 podman podman run --rm docker.io/library/alpine:3.23 echo hello from podman
hello from podman
$ docker exec -e PODMAN_IGNORE_CGROUPSV1_WARNING=1 podman podman run --rm alpine:3.23 echo hi
hi
$ docker exec -e PODMAN_IGNORE_CGROUPSV1_WARNING=1 podman podman build -q -t express-app /tmp/express-app
npm notice
npm notice New major version of npm available! 11.19.0 -> 12.2.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v12.2.0
npm notice To update run: npm install -g npm@12.2.0
npm notice
npm warn using --force Recommended protections disabled.
a5a0b2b95ea756ad73b92ead62ece1770fdb26033c69749933fe03e3988dcb8d
$ docker exec -e PODMAN_IGNORE_CGROUPSV1_WARNING=1 podman podman run -d --name app -p 3000:3000 express-app
74aeae727d68aacae32aee45d0705881fbea9b449ac1a5ee07c365b1490f42b3
$ docker exec -e PODMAN_IGNORE_CGROUPSV1_WARNING=1 podman podman exec app wget -qO- localhost:3000
{"message":"Hello from Express","node":"v24.21.0"}
$ docker exec -e PODMAN_IGNORE_CGROUPSV1_WARNING=1 podman su podman -c 'podman run --rm docker.io/library/alpine:3.23 id -u; podman info --format {{.Host.Security.Rootless}}'
0
true
podman run的用法与 Docker 相同,--rm、-d、-p等参数都能直接使用。podman build直接使用 Node.js 应用 的 Dockerfile,无需任何修改即可构建成功,容器也能正常响应。- 建议写出完整的镜像名称,例如
docker.io/library/alpine:3.23。Podman 的短名称(alpine:3.23)会根据系统配置决定从哪个 Registry 下载,不同发行版的配置可能不同。 - 最后切换为普通用户
podman运行:podman info显示rootless为true,容器内仍然是 uid 0,但和 Rootless Docker 一样,只对应主机上的普通用户。
安装
# Ubuntu / Debian
sudo apt-get install -y podman
# Fedora / RHEL
sudo dnf install -y podman
Windows 和 macOS 可以安装 Podman Desktop,它和 Docker Desktop 一样在虚拟机中运行 Linux。
如何选择 Docker、Rootless Docker 和 Podman
| Docker(普通) | Rootless Docker | Podman | |
|---|---|---|---|
| 常驻进程 | dockerd(root) | dockerd(普通用户) | 没有 |
| 容器内 root 的实际权限 | 主机 root(受隔离限制) | 普通用户 | 普通用户(rootless 时) |
| 与本教程命令的兼容性 | 完全相同 | 几乎相同 | 大部分相同 |
| Docker Compose | 内置 | 内置 | podman compose 调用外部实现 |
| 生态工具(Portainer、Traefik 等) | 完全支持 | 大多支持 | 需要另外配置 socket |
| 适用场景 | 一般开发、Docker Desktop、大多数服务器 | 多人共用的服务器、安全要求较高的场景 | RHEL / Fedora 环境、不想使用常驻进程的场景 |
对大多数初学者和一般项目来说,按照本教程使用 Docker 即可;如果服务器有多人登录,或安全要求较高,再考虑 Rootless Docker 或 Podman。
重点回顾
- 普通 Docker 的 dockerd 以 root 运行,能使用 Docker 就相当于拥有主机 root 权限。
- Rootless 模式使用 user namespace,让容器内的 root 映射为主机上的普通用户,实测无法读取主机的
/etc/shadow。 - Rootless 的主要限制是 1024 以下的端口、资源限制需要 cgroup v2,以及网络性能。
- Podman 没有常驻进程,默认采用 rootless,命令几乎与 Docker 相同,可以直接构建 Dockerfile。
动手练习
练习 1:在普通 Docker 中运行 docker run --rm alpine:3.23 id,再运行 docker run --rm -u 1000:1000 alpine:3.23 id。这和 Rootless 模式有什么不同?
答案
-u 1000:1000 只是让容器内的程序以 uid 1000 运行,dockerd 仍然是 root,能使用 Docker 的人仍可以不加 -u 再启动一个 root 容器。Rootless 模式则是整个 Docker Engine 都以普通用户运行,没有任何容器能获得主机的 root 权限。两者可以同时使用。
练习 2:在 Rootless Docker 中运行 docker run -d -p 80:80 nginx:1.30-alpine 失败了,有哪两种方法可以解决?
答案
- 改用 1024 以上的端口,例如
-p 8080:80,再用主机上的反向代理或防火墙规则把 80 转发到 8080。 - 调整系统配置,允许普通用户绑定 80 端口:
sudo sysctl net.ipv4.ip_unprivileged_port_start=80(写入/etc/sysctl.conf才能在重启后保留)。
常见问题
Rootless 模式会更慢吗?
运行容器内程序的速度差不多。网络(通过用户空间的网络转发)和部分文件系统操作会比普通模式慢一些,大多数网站和开发用途感觉不出来。
Docker Desktop 需要 Rootless 吗?
不需要。Docker Desktop 在 Windows、macOS 上把 Docker Engine 放在虚拟机里运行,本身就与电脑隔离。Rootless 主要用于 Linux 服务器和 Linux 桌面。
可以把 docker 命令直接换成 podman 吗?
大部分情况下可以,podman run、podman build、podman ps 的参数几乎相同,很多人直接设置 alias docker=podman。区别在于没有常驻进程、默认采用 rootless,以及 Compose、Docker socket 相关工具需要另外配置。
Podman 可以使用 compose.yaml 吗?
可以。podman compose 会调用系统上安装的 Compose 实现(docker-compose 或 podman-compose)来处理 compose.yaml,大部分配置都能使用。
更新于 2026年10月13日