一句话结论
rootful 与 rootless 不是“模式开关”,而是两个完全独立的 docker daemon(各自一套 socket、数据目录、镜像与容器,互不共享)。维护时第一件事不是“docker 坏了吗”,而是先确认你在跟哪一个 daemon 说话 —— 本机(max2-nixos)自 2026-10-11 起为 rootless-only,rootful 已不启用。
一、两者差异对照(客观项)
| 对比项 | rootful(系统级) | rootless(用户级) |
|---|
| daemon 进程属主 | root(uid 0) | 普通用户(本机:cat / uid 1000,由 rootlesskit 拉起) |
| 客户端 socket 路径 | /var/run/docker.sock | $XDG_RUNTIME_DIR/docker.sock(本机 /run/user/1000/docker.sock) |
| 数据目录(Docker Root Dir) | /var/lib/docker | ~/.local/share/docker(本机 /home/cat/.local/share/docker) |
| 镜像 / 容器 / 网络 / 卷 | 独立一套,与 rootless 互不可见 | 独立一套,与 rootful 互不可见 |
| systemd 单元位置 | 系统单元:docker.service / docker.socket(/etc/systemd/system) | 用户单元:systemctl --user ... docker.service(本机 /etc/systemd/user/docker.service → nix store) |
| 容器所属 cgroup | /system.slice/docker-*.scope | /user.slice/user-1000.slice/user@1000.service/user.slice/docker-*.scope |
| 开机/注销行为 | 开机即起;不依赖用户登录 | 需 Linger=yes(本机已开)+ 用户单元 enabled(本机已开)⇒ 可开机自启;否则登录才起 |
| 绑 <1024 端口 | 直接支持 | 受限(需 rootlesskit 端口转发或额外配置) |
| 使用 user namespaces | 不使用(容器内 root = 真实 root 映射) | 使用(容器内 root 只映射到普通用户) |
| 网络实现 | 直接使用宿主网络栈 | 多一跳(slirp4netns / pasta),端口转发由 rootlesskit 代理 |
| ‘主机’端口在宿主上的监听进程 | dockerd(root) | rootlesskit(普通用户) |
| 自动清理(autoPrune) | nixpkgs 选项 virtualisation.docker.autoPrune 可用 | 无对应机制(本机实测:全机无 autoPrune、无 prune 定时器) |
二、第一判据:现在到底在跟哪个 daemon 说话
① 数据目录(最直接、不受环境变量影响):
docker info --format '{{.DockerRootDir}}'
· /home/cat/.local/share/docker ⇒ rootless
· /var/lib/docker ⇒ rootful
② 客户端去哪连(环境变量决定):
echo "$DOCKER_HOST"
· unix:///run/user/1000/docker.sock 或 $XDG_RUNTIME_DIR/docker.sock ⇒ rootless
· 空(未设)⇒ 默认找 /var/run/docker.sock ⇒ 只有 rootful 在跑时才通
③ 看 daemon 进程属主:
ps -eo user,pid,args | grep -E 'dockerd|rootlesskit' | grep -v grep
· 属主 root 的 dockerd ⇒ rootful;属主是普通用户 + rootlesskit ⇒ rootless
④ 看容器归哪个 slice:
cat /proc/$(docker inspect -f '{{.State.Pid}}' 容器名)/cgroup | head -1
· /system.slice/... ⇒ rootful;/user.slice/user-1000.slice/... ⇒ rootless
三、本机现状(max2-nixos,2026-10-11 实测)
| 项 | 实测值 |
|---|
| 运行中的 daemon | 仅 rootless(系统 docker.service / docker.socket = inactive;/etc/systemd/system 下 docker 单元 0 个) |
| daemon 进程 | rootlesskit + dockerd,属主全为 cat(uid 1000) |
| socket | /run/user/1000/docker.sock(另有 rootful 时代残留的 /var/run/docker.sock 已于 2026-10-11 删除) |
| Docker Root Dir | /home/cat/.local/share/docker(792 MB) |
| rootful 残留数据 | /var/lib/docker 372 MB(无任何 daemon 使用;且 docker system df 看不到它) |
| 开机自启 | Linger=yes + 用户单元 enabled ⇒ 开机自动拉起 rootless |
| DOCKER_HOST 来源 | NixOS 生成于 /etc/set-environment(条件:未设且 XDG_RUNTIME_DIR 存在) |
| 自动清理 | 无(全机无 prune 定时器);当前 docker system df:3 镜像 827.9MB,可回收 0B |
| nix 侧配置 | modules/system/service/docker.nix:rootless.enable = true + setSocketVariable = true;另包 docker-compose + 别名 d = docker compose |
四、故障 → 根因 → 对应动作(维护常用)
| 现象 | 根因 | 动作 |
|---|
Cannot connect to the Docker daemon at unix:///var/run/docker.sock | DOCKER_HOST 未设(客户端找 rootful 的默认路径),而本机只有 rootless | source /etc/set-environment 或重开终端;或临时 export DOCKER_HOST=unix:///run/user/1000/docker.sock |
dial unix /var/run/docker.sock: no such file or directory | 同上,且路径本身不存在 | 同上(此报错比上面那条更易定位) |
permission denied while trying to connect to the Docker daemon socket | 用户在连 rootful socket 且不在 docker 组 | 确认是否该用 rootless(本机就应用 rootless) |
docker compose ps 报 no configuration file provided | compose 子命令要求当前目录有 compose 文件 | cd 到项目目录,或 docker compose -f <文件> ps |
| 镜像/容器“平白消失” | 切过 daemon(rootful ↔ rootless),另一套数据里没有 | 用第二节第①条判据确认数据目录;需要则重新拉取 |
| 磁盘只增不减 | 无 autoPrune;镜像层与构建缓存累积 | docker system df 看可回收 → docker system prune(加 -a 会删未使用镜像,需先确认) |
五、日常维护动作
看占用与可回收:
docker system df # 镜像/容器/卷/构建缓存四类
du -sh ~/.local/share/docker # rootless 数据目录真实占用
sudo du -sh /var/lib/docker # 另一套(若存在,说明跑过 rootful)
清理(按危险度递增):
docker system prune # 删停止的容器 + 悬空镜像 + 无主网络
docker system prune -a # ⚠ 连未被容器引用的镜像一起删
docker image prune # 只清镜像
看当前跑的实例(不依赖 compose 目录):
docker ps --format '{{.Names}} {{.Status}} {{.Ports}}'
看 compose 项目(需在项目目录或 -f 指定):
docker compose ps / docker compose logs -f
重启某个 daemon(会连带重启它名下所有容器,含代理类容器):
systemctl --user restart docker.service # rootless(本机在用的)
systemctl restart docker.service # rootful(本机已不启用)
六、rootless 的能力边界(以本机实例为证)
| 能力 | rootless 下表现 |
|---|
cap_add: NET_ADMIN | 可用(本机 clash 容器 CapAdd=[CAP_NET_ADMIN],运行正常) |
映射 /dev/net/tun | 可用(本机 clash 容器 Devices=[/dev/net/tun rwm]) |
| 自定义 bridge 网络 | 可用(本机 clash / clash-ui / mariadb 均在 ghost_net,按容器名互访正常) |
| 容器内进程以 root 身份运行 | 可以,但仅是 user namespace 内的 root(映射到宿主普通用户) |
--privileged、真 host 网络、网关/DHCP 类容器 | 受限或不可用(本机无此需求) |
| 绑 <1024 端口 | 需通过 rootlesskit 端口转发实现 |
| 多用户共享实例 | 不支持(每用户各自一套 daemon/数据) |
| 随 nix 世代回滚 | 不适用:镜像/容器/数据在用户目录,与 nix 世代无关 |
| 性能 | 网络多一跳(slirp4netns,本机 MTU 65520);本机实测 daemon 空闲 CPU≈0.02%、内存≈59MiB |
七、易踩的坑(本机真实发生过的)
| 坑 | 表现 | 规避 |
|---|
用 sudo docker ... 起容器 | 落到 rootful,留下一套数据与 /var/run/docker.sock(本机 372MB 就是这么来的) | 一律不加 sudo,用普通用户跑 docker compose up -d |
| 残留的 rootful socket 存在但无 daemon | 报“Cannot connect ... Is the daemon running?”(易误判为 docker 挂了) | 用第二节第①条判据确认;无用则删(本机已删) |
| 改了 compose 端口/socket 相关设置但没重建容器 | 宿主侧监听地址仍是旧的 | 改后 docker compose up -d 重建,再用 ss -lntp 验证 |
| 把 rootful 与 rootless 当成“同一个 docker 的两种启动方式” | 找不到镜像、端口被占、容器重复 | 记住它们数据完全不互通(第一节表第 4 行) |
八、相关笔记
- 「服务:nix收敛代理配置」
xi6ugMzM4rB0 —— mihomo 容器跑在本机 docker 上,代理故障的 30 秒定位手册在那里