正式環境為什麼需要 Rootless Docker?從 root 權限風險到可驗證部署

作者: 發布: 16 分鐘閱讀
所屬主題: 容器技術

查核資訊: 本文於 2026-07-21 查核 Docker 官方文件與 skill-rootless-docker 專案內容,並於 2026-08-29 再次確認 Rootless Docker 的權限模型、必要條件與 cgroup 限制。Docker 的支援版本、套件庫指令與各 Linux 發行版的安全限制可能變動,實際修改主機前請再次確認官方文件。

很多 Docker 教學都會把這行指令列為安裝後的第一件事:

sudo usermod -aG docker "$USER"

登出再登入,之後執行 docker compose up -d 就不用一直輸入 sudo。操作變方便了,看起來也像是「讓一般使用者管理 Docker」。

問題是,不用輸入 sudo,不代表你沒有 root 權限。

Docker 官方的 Linux post-installation 文件直接警告:docker group 會授予使用者 root-level privileges。原因不是群組名稱特別危險,而是傳統 Docker Engine 的 daemon 本身以 root 執行;只要能控制它的 socket,就能要求它建立容器、掛載 host 路徑、操作網路與啟動高權限 workload。

在自己的開發機上,這個取捨有時可以接受。但到了長時間對外服務、保存正式資料,而且可能被 CI/CD 或管理工具操作的 production host,這條權限捷徑值得重新檢查。

我把這套檢查、安裝、遷移與驗證流程整理成 skill-rootless-docker。它的目標不只是把 Rootless Docker 裝起來,而是避免 agent 在沒有盤點 workload、沒有說明影響、也沒有 rollback 的情況下,直接改掉一台正式主機。

這篇先回答最重要的問題:Rootless Docker 究竟降低了什麼風險?哪些 production workload 適合用?又要怎麼確認它真的部署完成,而不只是 installer 顯示 success?

問題不在 sudo,而在 daemon 的權限

Docker CLI 並不直接建立 container。當你執行 docker run 或 docker compose up,CLI 會透過 Docker API 把要求送給 daemon,再由 daemon 執行真正的工作。

傳統 rootful Docker 的關係大致是:

developer / deployer
        │ Docker CLI
        ▼
/var/run/docker.sock
        │
        ▼
dockerd(host root)
        │
        └─ 建立 containers、掛載 host 路徑、設定網路與 cgroup

Unix socket 可以限制「誰能連線」,卻不會把連線後能呼叫的 Docker API 自動變成低權限版本。Docker Engine security 文件也特別舉例:能控制 daemon 的使用者,可以啟動一個把 host / 掛進 container 的 workload,進而修改 host filesystem。

因此,把 deployment account 加進 docker group,本質上不是授予「部署這個 app」的權限,而是授予「控制 rootful daemon」的權限。差距非常大。

這也解釋了另一個常見誤會:

volumes:
  - /var/run/docker.sock:/var/run/docker.sock:ro

:ro 只讓 container 不能改寫 socket 這個 filesystem entry,不會把送進 socket 的 POST /containers/create 等 API request 變成唯讀。只要應用程式能對 socket 發出有效要求,它仍然可以控制 daemon。若某個 dashboard、CI runner 或自動更新工具真的需要 Docker API,必須把它視為 daemon administrator;只需要少量操作時,應考慮有 allowlist 的 socket proxy,而不是因為寫了 :ro 就當作風險消失。

Rootless Docker 改變了哪一層?

Docker 的 Rootless mode 會把 Docker daemon 和 containers 一起放進 user namespace,兩者都不以 host root 執行。

deployer(一般使用者)
        │ Docker CLI / rootless context
        ▼
/run/user//docker.sock
        │
        ▼
dockerd(由 deployer 擁有,位於 user namespace)
        │
        └─ containers(仍受同一個非 root 權限邊界限制)

Rootful Docker 與 Rootless Docker 的權限邊界比較,顯示 socket、daemon 與 container 在 host root 或非 root user namespace 下的差異。

user namespace 會把 container 看到的 UID/GID 映射到 host 上的一段 subordinate IDs。Container 裡的 process 可以看起來是 UID 0,但那個 UID 0 不等於 host 的 UID 0。

Rootless Docker 需要 newuidmap、newgidmap,而且目標使用者在 /etc/subuid 與 /etc/subgid 都要有至少 65,536 個 subordinate IDs。這些 range 不能靠複製教學裡常見的 100000:65536 隨便填;正式主機可能已有其他使用者、container runtime 或集中式身分管理配置,重疊的 range 必須先排除。

它和「container 內不用 root」不同

在 Dockerfile 寫 USER app 是很好的做法,但它只控制 container 內應用程式用哪個帳號執行。若背後仍是 rootful daemon,daemon 本身依然持有 host root 權限。

兩者應該疊加,而不是二選一:

  • Container 內使用 non-root user,降低應用程式被入侵後能做的事。
  • Docker daemon 使用 rootless mode,降低 daemon 或 runtime 邊界被突破後對 host 的影響。
  • 再搭配最小 capabilities、seccomp、唯讀 filesystem、secrets 管理、image 更新與網路限制。

它和 userns-remap 也不同

userns-remap 同樣會重新映射 container 內的 UID/GID,但 Docker 官方文件指出,這個模式下 daemon 仍以 root 執行。Rootless mode 則連 daemon 本身都不持有 root 權限。

所以兩者處理的是不同深度的邊界:userns-remap 主要降低 container process 對 host UID 的權限;rootless 更進一步移除 daemon 的 host root 身分。

為什麼 production 特別需要評估 rootless?

我不會說每一台 production host 都能無條件改成 rootless。Kubernetes node、需要 host device 的 workload、複雜網路設備或依賴特定 kernel controller 的服務,都需要另外設計。

但對常見的「一台 Linux VM 加 Docker Compose」部署,Rootless Docker 應該從額外 hardening 選項,提升為預設評估項。

1. Production 的暴露時間與資料價值都更高

開發機可能只在工作時啟動,資料也能重建;production daemon 則長時間執行、接收外部流量、掛載 persistent volumes,還可能保存 database、上傳檔案與 deployment credentials。

Rootless 不會修掉應用程式漏洞,但當攻擊鏈跨過 container 或 Docker runtime 邊界時,daemon 沒有 host root 身分,能減少攻擊者直接取得整台主機最高權限的機會。這是 defense in depth:它不假設上一層永遠不會失敗,而是限制失敗之後的 blast radius。

2. Deployment account 不必同時成為系統管理員

理想的 production 權限分工是:

  • administrator 負責套件、kernel、firewall、SSH 與 system service;
  • deployer 負責自己的 rootless daemon 與 applications;
  • application account 只取得執行服務需要的檔案與 secret。

日常部署因此不必使用 sudo,也不必讓 deployer 加入 rootful docker group。真的需要 host-level 變更時,再由獨立 administrator 執行一組已審核、可 rollback 的命令。

這個差異在自動化環境尤其重要。CI credential 外洩時,如果它只能控制某個 deployment account 的 rootless daemon,和它能控制 host root daemon,是兩種完全不同的事故範圍。

3. 權限邊界會變得更容易說明與稽核

Rootless daemon 的 socket 通常位於 /run/user/<uid>/docker.sock,資料預設放在 ~/.local/share/docker,設定則位於 ~/.config/docker。Service 由 systemctl --user 管理。

這讓「誰擁有 daemon、誰能連 socket、資料放在哪裡、服務如何啟動」都能對應到一個明確的 Linux account。比起所有 deployment automation 共用 /var/run/docker.sock,權限模型更容易被檢查。

不過別把 rootless socket 當成普通 IPC。拿到它的人依然能控制該使用者的所有 rootless containers,也可能讀到 containers 能存取的 secrets 與 bind mounts。Rootless 降低的是 host privilege,不是把 daemon API 變成低風險 API。

為什麼我把流程做成一個 skill?

Rootless Docker 的安裝命令不長,真正麻煩的是命令前後的決策。

  • 主機是不是原生 Linux,而且以 systemd 運作?
  • 要沿用既有 account,還是建立專用 deployer?
  • 目前有沒有 rootful containers、volumes 或 systemd services?
  • 新舊 daemon 要 coexist,還是正式 migration?
  • Workload 是否依賴 80/443、Docker socket、bind mounts、devices 或嚴格的 cgroup limits?
  • User service 能不能跨過 logout,並在 reboot 後自動啟動?

如果 agent 一看到「幫我裝 Rootless Docker」就直接跑安裝,很容易把唯讀盤點直接變成主機修改,甚至停掉還在提供服務的 rootful daemon。

rootless-docker-setup 把工作拆成固定順序:

  1. Read-only preflight:先收集 host 與 target user 狀態,不安裝套件、不用 sudo、不修改 service。
  2. Change plan:把已正確、必要、可選與不相容項目分開,列出命令、影響與 rollback。
  3. Explicit approvals:套件、daemon migration、networking、access hardening 分組確認。
  4. Target-user installation:setup tool 一律由真正的 non-root login session 執行。
  5. End-to-end validation:檢查 context、socket、process owner、Compose、user service、linger、container 與對外連接埠。

Rootless Docker 安全部署流程,從只讀 preflight、變更計畫、明確核准、目標使用者安裝到端對端驗證,失敗時停止診斷或回滾。

可以透過 skills CLI 安裝:

npx skills add FWcloud916/skill-rootless-docker \
  --skill rootless-docker-setup

或安裝成 Codex plugin:

codex plugin marketplace add FWcloud916/skill-rootless-docker
codex plugin add rootless-docker@fwcloud916

接著不要一開始就要求它安裝,先要求 audit:

Use $rootless-docker-setup to audit this Ubuntu host for rootless Docker
readiness for user deployer. Do not change the host.

這句 Do not change the host 並不是因為 skill 本身沒有安全規則,而是讓任務意圖再次變得明確:現在是盤點,不是安裝。

一條適合 Ubuntu production 的落地路線

以下不是可以直接複製到任何主機的萬用 script,而是一條變更順序。套件庫與支援版本會變動,實際安裝前仍要以 Docker 官方 Ubuntu 文件 為準。

第一步:選定 account 與 migration 策略

先指定一個 non-root account,例如 deployer。如果現有帳號已符合權限隔離目標,可以直接沿用;建立新帳號、安裝 SSH key 或移除 sudo 都是獨立的 access change,不能和 Docker 安裝一起默默完成。

接著明確選擇:

  • Fresh setup:主機沒有既有 Docker workload。
  • Coexistence:rootful 與 rootless daemon 暫時並存,靠 Docker context 明確選擇。
  • Migration:盤點並停妥既有 workload 後,停用 rootful docker.service 與 docker.socket。

Migration 前至少要從 administrator context 盤點:

sudo docker ps -a
sudo docker image ls
sudo docker volume ls
sudo docker network ls
sudo systemctl list-dependencies --reverse docker.service
sudo ss -lntup

這些是 read-only inventory,但因為會使用 sudo,仍應讓主機擁有者先看到命令與目的。還要搜尋 deployment scripts、CI 與其他 system services 是否寫死 /var/run/docker.sock。

不要把 rootful /var/lib/docker 直接搬進 ~/.local/share/docker,也不要在「migration」名義下刪除舊資料。Image 可以重拉,volume 與 database 則需要 workload-specific 的備份和搬遷方案。

第二步:跑 read-only preflight

Skill 內附的 preflight.sh 會要求明確 target user:

bash scripts/preflight.sh --user deployer

輸出使用 check.<name>=<status>:<detail> 格式,最後會得到:

  • overall=ready:基礎條件齊全;
  • overall=needs-attention:有缺少套件、subordinate IDs、cgroup 或衝突狀態;
  • overall=unsupported:host class 不在 skill 支援範圍。

它會檢查 distribution、systemd、target account、newuidmap、newgidmap、subordinate IDs、rootful units 與 socket、rootless context、user socket、linger、cgroup v2、AppArmor/SELinux、firewall tools,以及特權連接埠(1024 以下)的使用政策。

needs-attention 不是「可以加 --force 跳過」,而是 change plan 的輸入。每個 attention 都要先判斷它是安裝前提、workload-specific 限制,還是刻意保留的 coexistence 狀態。

第三步:準備官方套件與 rootless prerequisites

Ubuntu 通常需要 uidmap、dbus-user-session、slirp4netns、fuse-overlayfs,以及 Docker 官方套件中的 docker-ce-rootless-extras。後者提供 dockerd-rootless-setuptool.sh 與所需整合。

新增 apt repository、安裝或移除套件都會修改 host,必須先展示:

  • 完整命令與 repository URL;
  • 會新增或移除哪些 packages;
  • 是否可能自動啟動 rootful Docker;
  • 失敗與 rollback 方式。

套件安裝完成後要立刻檢查 docker.service 和 docker.socket。Ubuntu 的 Docker packages 可能啟動 rootful system service;這不代表應該馬上停掉它,而是要回到前面核准過的 coexistence 或 migration 決策。

也不要為了方便改用 curl ... | sh。Skill 優先採用 Docker 的 signed distribution packages;只有透過套件安裝不可行時,才下載官方 standalone installer 到具名檔案,讓使用者檢查後另行批准。

Ubuntu、Debian 與 Fedora 的整體流程相同,但不要混用 package commands:

Distribution Rootless prerequisites 重點 安裝前特別確認
Ubuntu uidmap、dbus-user-session、slirp4netns、fuse-overlayfs 新版 Ubuntu 的 unprivileged user namespace 與 AppArmor 狀態
Debian 與 Ubuntu 類似,使用 Debian 專屬 Docker repository Derivative distribution 不能自行假設能沿用 Debian codename
Fedora newuidmap、newgidmap 通常由 shadow-utils 提供,另檢查 slirp4netns、fuse-overlayfs、iptables DNF command 版本、repository signing key 與 SELinux 狀態

實際命令應分別回到 Docker 官方的 Ubuntu、Debian 或 Fedora 文件核對。Skill 也只會在 preflight 確認 ID 後載入對應 reference,不會把 Debian derivative 或其他 RPM distribution 當成等價環境。

第四步:用 target user 的真實 login session 安裝

Rootless service 不是一個寫了 User=deployer 的 system-wide service。Docker 官方的 Rootless tips 明確說明,這種模式不受支援。

應透過 SSH 或真正的 login session 進入 target user,先確認環境,再執行 setup:

whoami
id -u
printf 'HOME=%s\nXDG_RUNTIME_DIR=%s\n' "$HOME" "$XDG_RUNTIME_DIR"
dockerd-rootless-setuptool.sh check
dockerd-rootless-setuptool.sh install

正常情況下,setup tool 會建立 rootless Docker context 與 user service。優先使用 context:

docker context use rootless
docker context inspect rootless

只有不支援 context 的程式才設定:

export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock"

不要把 DOCKER_HOST 無條件寫進全域 shell 設定,否則它會覆蓋 context selection,之後在 rootful/rootless coexistence 或操作其他 remote context 時很容易連錯 daemon。

第五步:讓 service 跨過 logout 與 reboot

Production service 不能在 deployer 登出後跟著消失。先說明 user lingering 會讓該帳號的 user manager 在沒有登入 session 時持續存在,取得核准後,由 administrator 執行:

sudo loginctl enable-linger deployer

再由 deployer 執行:

systemctl --user enable --now docker.service

這兩個動作分屬 host policy 與 user service lifecycle,不要因為常被寫在同一段教學裡,就當成同一個無須說明的步驟。

第六步:處理 80/443 特權連接埠與 firewall

Rootless process 預設不能直接綁定特權連接埠。最單純的設計是讓 container 發布 8080/8443,再由 host reverse proxy 或外部 load balancer 接 80/443。

如果 rootless daemon 必須直接發布 80/443,Docker 文件提供兩類做法:

  • 調低 net.ipv4.ip_unprivileged_port_start:影響 host 上所有 unprivileged processes,範圍較大。
  • 對實際的 RootlessKit binary 設定 CAP_NET_BIND_SERVICE:範圍較窄,但 package upgrade 後可能需要重新檢查。

兩者都不是 base installation 的附帶步驟。要先確認需求、顯示 persistent config 或 binary path、取得 approval,再修改 sysctl 或 capability。

Firewall 也一樣。偵測到 ufw、firewalld、nft 或 iptables,不等於它正在管理這台主機,更不代表可以自動開放 80/443。應先讀取現有 policy,只新增實際需要、來源範圍最小的規則,最後從外部 client 驗證。

Installer 顯示成功,還不算完成

正式環境最常見的錯誤,是看到 dockerd-rootless-setuptool.sh install exit code 0 就宣布完成。

至少要在 target user 的 login session 驗證:

docker context show
docker info
docker compose version
systemctl --user is-enabled docker.service
systemctl --user is-active docker.service
loginctl show-user "$(id -un)" -p Linger
pgrep -u "$(id -u)" -af 'dockerd|rootlesskit'
docker run --rm hello-world

重點不是每道命令都有輸出,而是證據彼此一致:

  • active context 指向 rootless user socket;
  • docker info 的 Security Options 包含 rootless;
  • daemon process 屬於 target user;
  • Compose 可用;
  • user service 已 enable 且 active;
  • linger 符合 reboot 後啟動需求;
  • 實際 container 能執行;
  • 對外服務能依預期經過 firewall path 連線;
  • rootful daemon 是刻意停用,或刻意 coexist。

最好再實際登出後重新登入,或重開機驗證。否則你測到的可能只是當前 shell 留下來的環境,不是能在主機重啟後自動恢復的 production service。

Rootless 的代價與不適用情境

Rootless 是更小的 host privilege,不是免費且透明的相容模式。

cgroup resource limits 可能沒有完整生效

Docker 官方的 Rootless tips說明,rootless mode 的 --cpus、--memory、--pids-limit 等 cgroup flags,需要 cgroup v2 與 systemd。即使條件成立,systemd 預設也可能只將部分 controllers 委派給非 root 使用者。

因此,在 docker compose.yml 設定 limit,不代表 kernel 一定會強制執行。需要 CPU、cpuset、I/O、memory 或 pids 控制的 workload,要檢查 docker info、可用 controllers 與 user service delegation。Docker 若警告某個 limit 被忽略,就不能把它列為已完成的 production guardrail。

Bind mount 會遇到 UID/GID mapping

Container 內的 UID/GID 映射到 subordinate range 後,既有 host directory 的 ownership 可能不再符合預期。修正時應針對 workload 真正使用的窄路徑處理,不要為了讓 container 寫入,對大範圍目錄遞迴 chown 或開放權限。

特殊網路、device 與 orchestration 要另外評估

Rootless networking、特權連接埠、host firewall、device access 和嚴格 resource controls 都可能需要額外整合。Docker Desktop、沒有適當 systemd user session 的 WSL、non-systemd 或 immutable hosts,也不適合直接照這篇的 service 流程操作。

Kubernetes 與多節點 orchestration 更不是「把每台 host 改成 Rootless Docker」就完成。它們有自己的 runtime、node privilege、networking、storage 與 workload security model,應另做平台層級的設計。

Rootless 也不會阻止 target user 傷害自己的資料

如果 deployer 本來就能寫入 /srv/my-app/data,那麼由它擁有的 rootless containers 也可能透過核准的 bind mount 修改這些資料。攻擊者拿到 rootless socket 後,仍能操作這個 daemon 及其可存取資源。

所以最精準的說法不是「Rootless Docker 讓 container 變安全」,而是:

Rootless Docker 把 Docker daemon 與 containers 從 host root 權限移開,讓一層安全控制失敗時,不必直接等同整台主機最高權限失守。

一張 production 決策表

情境 建議
單機 Linux VM、Docker Compose、一般 Web/API/DB 優先評估 rootless,通常是很好的預設
可由 reverse proxy 接 80/443 Rootless 很適合,container 使用 host 上的高號連接埠
既有 rootful workload 正在服務 先 inventory,選 coexistence 或分階段 migration
CI 或 dashboard 需要 Docker socket 仍視為 daemon admin;限制 account、socket 與可掛載資料
嚴格 CPU/I/O limits 先驗證 cgroup v2 與 controller delegation
大量既有 bind mounts 先測 UID/GID mapping 與 ownership
需要 host devices、特殊 networking 或 privileged workload 不要強套,改做 workload-specific security review
Kubernetes、多節點或非 systemd host 不套用本文流程,使用平台專屬設計

收尾:把 rootless 當成預設問題,而不是預設答案

正式環境要問的,不應只是「Docker 有沒有跑起來」,而是「這個 daemon 為什麼需要 host root?」

如果答案只是因為安裝教學一直這樣做,那就值得跑一次 read-only audit,盤點實際需求。對一般的單機 Linux 與 Docker Compose 部署,Rootless Docker 往往能用可接受的整合成本,換到更清楚的 account boundary 與更小的 host blast radius。

但別跳過限制,也別把 migration 當成一條停服務的指令。先 inventory、把 privileged changes 分組、逐項確認 rollback,最後用 context、socket、process owner、service lifecycle 與真實 workload 證明它能在 production 運作。

這正是 skill-rootless-docker 想固定下來的流程:不是讓 agent 更快取得 root,而是讓它知道什麼時候不該碰 root。

參考資料


系列:Docker 與容器化工程:從原理、Dockerfile 到 Production 安全