目录

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 DockerPodman
常驻进程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 失败了,有哪两种方法可以解决?

答案
  1. 改用 1024 以上的端口,例如 -p 8080:80,再用主机上的反向代理或防火墙规则把 80 转发到 8080。
  2. 调整系统配置,允许普通用户绑定 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日