Podman 與 Docker 在 VPS 上有何不同?
Podman 預設以 rootless 模式執行且沒有 daemon;本文說明這對 compose 檔、quadlet、低於 1024 的埠與 volume 擁有權的實際影響。
Podman 與 Docker 實際上有何不同
Podman 與 Docker 都能在 VPS 上執行相同的 OCI(open container initiative)映像,因此選擇的重點不在於能執行哪些軟體,而在於程序模型。Docker 會執行一個擁有所有容器的 root daemon,而 docker 命令只是用來要求該 daemon 執行工作的輕量型用戶端。Podman 沒有 daemon:podman run 會在呼叫它的程序下,以目前未具特殊權限的使用者身分,將容器啟動為子程序。
其他差異都源自這項基本設計。自動啟動改由 systemd 負責,而不是由 daemon 負責。Volume 的擁有權會經過 user namespace 對映,因此在主機上使用 ls -l 看到的擁有者,與容器內看到的擁有者不同。低於 1024 的埠在變更核心設定前無法繫結。docker CLI(command line interface)可透過 wrapper 繼續運作,但只要有元件需要 Docker socket,就會遇到限制。
沒有 daemon:啟動容器時實際執行的內容
在 Docker 主機上,pstree -a 會顯示由 root 執行的 dockerd、其旁的 containerd,以及每個執行中容器各自對應的一個 containerd-shim-runc-v2。您的應用程式是該 shim 的子程序,而 shim 是 PID 1 的子程序。容器與啟動它的 shell 之間沒有連線。停止 daemon 後,主機上所有容器的控制平面都會失效;若預設的 live-restore 設定為關閉,systemctl restart docker 也會重新啟動您的容器。
Podman 沒有對應的程序。啟動容器後,會取得一個 conmon(容器監控程序),負責持有容器的主要程序,且由執行該命令的使用者擁有。
podman run -d --name web -p 8080:80 docker.io/library/caddy:2
ps -o user,pid,ppid,args -C conmon
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080ps 應列出由您的登入使用者執行、而不是由 root 執行的 conmon,而 curl 應輸出 200。由於沒有集中式服務負責管理容器,sudo apt upgrade podman 不會停止已在執行的容器;某個容器的監控程序當機,也不會連帶影響其他容器。
缺少 daemon 也會帶來代價。重新開機後,沒有任何元件會啟動您的容器。Docker 的 --restart=always 是 daemon 在開機時履行的保證;Podman 則以 systemd 取代它,下面的 quadlet 章節就是用來說明這部分。
socket 是另一個重點。/var/run/docker.sock 是由 root 擁有的 API(應用程式介面)端點;任何能寫入該端點的程序,都能啟動掛載主機檔案系統的特權容器。將使用者加入 docker 群組,等於透過較迂迴的方式授予該使用者 root 權限;這點值得搭配閱讀只授予每個服務帳號所需的存取權限。除非您明確要求,Podman 不會公開 socket;取得的 socket 會隸屬於 /run/user/<uid>/podman/podman.sock 的單一使用者。
在 Ubuntu 24.04 上安裝 Podman,並確認 rootless 確實生效
sudo apt update
sudo apt install -y podman uidmap
podman --version
podman info | grep -i rootlessuidmap 套件提供 newuidmap 與 newgidmap。這些是 setuid 輔助程式,可讓一般使用者取得一段 subordinate ID 範圍。沒有這些程式,rootless 容器就無法啟動。podman info 應輸出 rootless: true。
截至 August 2026 的檢查結果,Ubuntu 24.04 提供 Podman 4.9,Debian 13 提供 Podman 5.x。版本差異很重要,因為 quadlet 檔案需要 4.4 或更新版本,而 .pod quadlet 檔案需要 5.0。在從上游文件複製範例前,先執行 podman --version。
每個 rootless 使用者都需要一段 subordinate ID 範圍:
grep "$USER" /etc/subuid /etc/subgid在 Ubuntu 上,adduser 建立的使用者會自動取得範圍。useradd -M 建立的使用者,或由設定工具建立的使用者,通常不會自動取得範圍,錯誤訊息會明確指出這點:
Error: cannot find UID/GID for user deploy: no subuid ranges found for user "deploy" in /etc/subuid - check rootless mode in man pages.指派範圍後,重設該使用者的儲存區,讓系統套用新的對映:
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 deploy
podman system migrate首次執行時還有一個常見問題:Podman 不會預設使用 Docker Hub。短映像檔名稱會依據 /etc/containers/registries.conf 中的 unqualified-search-registries 解析;在未連接終端機的指令碼中,pull 會因 short-name resolution enforced but cannot prompt without a TTY 而失敗。每次都寫入完整名稱。請使用 docker.io/library/nginx:1.27,不要使用 nginx。
租用伺服器上,rootless 容器實際能帶來什麼
rootless 容器會在 user namespace 中執行。這是核心提供的功能,可為程序建立專用的使用者 ID 對映。在 namespace 內,容器的超級使用者是 UID (user ID) 0。在 namespace 外,也就是 VPS 上,同一個程序只是一般登入使用者。容器中的 root 並不是主機上的 root。
這就是它實際提供的安全效益。若映像檔要求以 root 執行,Web 應用程式存在遠端程式碼執行漏洞,或容器逃逸必須在外部具備 UID 0,這些情況最後取得的都是未具特權使用者的權限,而不是整台機器的權限。rootless 無法防護核心漏洞,也無法保護你自己的檔案,因為逃逸後的程序會以你的身分執行,並能讀取你可以讀取的所有內容。隔離的執行單元至少和 UID 對映同樣重要。下一節會看到另一種更容易理解的做法:FreeBSD jail,它封裝整個由你管理、如同小型機器的 userland,而不是從 registry 拉取的分層映像檔。
Docker 也能以 rootless 模式執行。dockerd-rootless-setuptool.sh install 會設定每位使用者專用的 daemon,而且運作良好。差異在於預設方向不同。使用 Podman 時,預設就是 rootless,不需另外指定。因此你最先遇到的問題會是容器無法繫結 port 80,而不是某個服務悄悄以 root 身分執行了兩年。
為什麼我的 volume 檔案擁有者是 UID 100999?
原因是同一個 user namespace。Container UID 0 會對應到主機 UID。Container UID 1 會對應到 subuid 範圍中的第一個 ID,之後依序遞增。若範圍從 100000 開始,container UID 1000 在主機上就會對應到 100999。
mkdir -p "$PWD/data"
podman run --rm --user 1000 -v "$PWD/data:/data" docker.io/library/alpine:3 sh -c 'id -u; touch /data/f'
ls -ln "$PWD/data"Container 顯示 1000。主機上的清單顯示擁有者為 100999,因為 100000 加上 1000 再減去 1 就是 100999。這不是故障,單純執行 chown 也無法修正,因為你的非特權使用者在 namespace 外完全無法變更檔案擁有權。
有 4 種處理方式:
podman unshare chown 1000:1000 "$PWD/data"會在相同的 user namespace 內執行 chown,此時數字的意義與 container 內一致。-v "$PWD/data:/data:U"會要求 Podman 代為修正來源目錄的擁有權。請對全新的目錄使用,不要用在重要資料上。--userns=keep-id會將主機 UID 對應到 container 內的相同 UID,讓新建立的檔案由你擁有。- 使用
-v appdata:/data等 named volume 可避開這個問題,因為 Podman 會在你自己的儲存區內建立 volume,並設定正確的擁有權。
如果你曾在 Docker 中處理過這個問題,這是同一個問題,只是多了一層。許多映像檔提供的 PUID 和 PGID 變數會設定 container 內程序使用的 UID;在 rootless Podman 中,該 UID 接著還會再對應一次。rootless container 內的 PUID=1000 仍會建立由 100999 擁有的主機檔案。設定數字時,請將第二次對應納入考量;或者將資料移至 named volume,省去這項管理。
掛載還有 2 點需要注意。在 Fedora 和 RHEL 範例中看到的 :z 與 :Z 旗標是 SELinux 重新標籤選項,而 Ubuntu 使用 AppArmor,因此這些旗標在 Ubuntu 上不會發揮作用。Rootless Podman 也無法掛載使用者無法讀取的主機目錄;這是預期的權限限制,不是故障。
為什麼 rootless Podman 拒絕發布 80 埠?
因為繫結 1024 以下的埠需要使用者目前沒有的權限。錯誤訊息會指出修正方式:
Error: rootlessport cannot expose privileged port 80, you can add 'net.ipv4.ip_unprivileged_port_start=80' to /etc/sysctl.conf (currently 1024), or choose a larger port number (>= 1024): listen tcp 0.0.0.0:80: bind: permission denied有兩種可行作法。降低整台主機的門檻:
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-podman.conf
sudo sysctl --system
sysctl net.ipv4.ip_unprivileged_port_start最後一個命令應回傳 80。請明確了解此設定的作用:現在機器上的每個使用者都能繫結 80 和 443,而不只有執行容器的使用者。在只有單一管理員的 VPS 上,這是可接受的取捨。但在承載其他使用者帳號的主機上則不適用。另一種作法是發布到 8080,再在前方放置反向代理;這也正是你要讓 certbot 在 nginx 上申請與續期憑證 的位置。
Rootless 發布也會改變應用程式看到的內容。Podman 4.x 預設搭配 rootlesskit port handler 使用 slirp4netns,轉送的連線會改寫來源位址,因此存取日誌會將每位訪客記錄為 10.0.2.100。Podman 5.0 將預設值改為 pasta,可保留實際的用戶端位址。在 4.x 上,--network slirp4netns:port_handler=slirp4netns 可恢復真實來源位址,但會降低部分輸送量。
這裡有一個值得注意的優點。Rootless 發布的埠是由一般程序擁有的普通 listening socket,因此防火牆的輸入規則會套用到它。Docker 會透過寫入 NAT(network address translation)規則及自身的 forwarding accept 規則來發布埠,這正是 已發布的 Docker 埠會忽略你以為能封鎖它的 ufw 規則 的原因。Rootful Podman 使用類似的網路配置,也會遇到相同陷阱。Rootless 則不會。
Docker Compose 檔案仍可在 Podman 下運作嗎?
大多數情況可以,主要有兩種方式。第一種是使用 podman-compose。這是獨立的實作,會讀取相同的檔案,並驅動 Podman CLI:
sudo apt install -y podman-compose
podman-compose up -d
podman ps第二種是使用真正的 Docker Compose,透過每位使用者的 socket 與 Podman 相容 Docker API 通訊:
systemctl --user enable --now podman.socket
export DOCKER_HOST="unix:///run/user/$(id -u)/podman/podman.sock"
docker compose up -d
podman psdocker compose ps 和 podman ps 應列出相同的容器,因為實際上只有一組容器。名稱解析也能正常運作:Podman 的預設網路後端 netavark 會執行 aardvark-dns,因此使用者自訂網路上的容器可以透過名稱找到彼此。
但仍有一些限制。任何掛載 /var/run/docker.sock 的設定,都必須改為指向 Podman socket,或移除該掛載。network_mode: host 在 user namespace 下的行為不同。depends_on 搭配 condition: service_healthy 在不同的 podman-compose 版本中,支援程度不一致。restart: always 不會自行在重新開機後恢復,下一節會處理這個問題。Compose 仍適合用單一檔案描述 多容器堆疊;在 Podman 下,它是轉換層。若某個堆疊預計維護多年,請將其轉換為 quadlets,並維護單一抽象層,而不是同時維護兩種抽象層。
Pod:Docker 無法提供的概念
Pod 是一組共用同一個 network namespace 的容器。Podman 會啟動一個小型 infra 容器,讓該 namespace 持續存在,之後各成員可透過 127.0.0.1 互相連線,不需要使用者自訂 network,也不涉及 service discovery。
podman pod create --name app -p 8080:80
podman run -d --pod app --name app-cache docker.io/library/redis:7
podman run -d --pod app --name app-web docker.io/library/nginx:1.27
podman pod ps
podman ps --podpodman pod ps 應顯示包含 3 個容器的 pod Running,其中包括 infra 容器。現在,web 容器會透過 127.0.0.1:6379 連線到 Redis,而不是透過 app-cache:6379。共用 namespace 會產生 2 項規則:在 pod 上發布連接埠,不要在成員容器上發布;此外,任何 2 個成員都不得監聽同一個連接埠。
這就是 Kubernetes 的模型,而 Podman 對此採取了完整支援。podman kube generate app > app.yaml 會根據目前執行中的內容產生 Kubernetes manifest(較舊的套件會將其寫成 podman generate kube),而 podman kube play app.yaml 可在另一台主機上重新建立該環境。Quadlet 提供一種 .kube unit type,可將這類檔案作為 systemd 服務執行。這是截然不同的服務分組方式;如果未來任何時候可能使用 Kubernetes,這也是選擇 Podman 的最充分理由。
無 daemon 的自動啟動:quadlet units
Quadlet 是 systemd generator。它會將描述容器的簡短檔案轉換為開機時使用的實際 systemd service。rootless user 的檔案放在 ~/.config/containers/systemd/,root 的檔案則放在 /etc/containers/systemd/。
~/.config/containers/systemd/caddy.container:
[Unit]
Description=Caddy web server
After=network-online.target
[Container]
Image=docker.io/library/caddy:2
ContainerName=caddy
PublishPort=8080:80
Volume=caddy-data.volume:/data
Environment=TZ=UTC
AutoUpdate=registry
[Service]
Restart=always
MemoryMax=512M
[Install]
WantedBy=default.target~/.config/containers/systemd/caddy-data.volume 幾乎可以是空的,因為 section header 就會建立 volume:
[Volume]systemctl --user daemon-reload
systemctl --user start caddy
systemctl --user status caddy
journalctl --user -u caddy -n 50service 名稱來自檔名:caddy.container 會成為 caddy.service。不要執行 systemctl --user enable caddy。產生的 unit 無法啟用,systemd 會回覆 Failed to enable unit: Unit /run/user/1000/systemd/generator/caddy.service is transient or generated.。[Install] section 會在開機時啟動容器,而 daemon-reload 會在你編輯檔案後重新產生 unit。
接著是幾乎所有人都會忽略的設定:
sudo loginctl enable-linger deploy
loginctl show-user deploy --property=Linger預期會看到 Linger=yes。未啟用 linger 時,最後一個 SSH 連線關閉後,systemd 會清除整個 user session,因此所有 rootless container 都會隨之停止,且不會在開機時重新啟動。登出後就消失的 container,原因一定是這個。
由於容器是一般 service unit 的主要程序,systemd 本身的控制機制會直接套用。[Service] section 中的 MemoryMax= 和 CPUQuota=,行為與 任何其他由 systemd 管理的 service 完全相同。這需要 cgroup v2(control group version 2);Ubuntu 自 22.04 起預設使用此版本。使用 podman info | grep -i cgroup 確認。
更新也有對應的機制。AutoUpdate=registry 加上 systemctl --user enable --now podman-auto-update.timer 會檢查 registry 上相同 tag 是否有較新的 image,重新啟動 unit;如果新 container 無法啟動,則回復到先前的 image。先執行 podman auto-update --dry-run,查看它會進行哪些變更。較舊的 podman generate systemd command 仍然存在,但已 deprecated,因此新的設定請使用 quadlet。
docker 別名可運作與無法運作的範圍
sudo apt install -y podman-docker
sudo touch /etc/containers/nodocker
docker pspodman-docker 會安裝一個 /usr/bin/docker wrapper,用來呼叫 Podman。若沒有 nodocker 檔案,每次呼叫都會先顯示 Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.。此 wrapper 涵蓋日常使用的指令:run、ps、logs、exec、build、pull、push、inspect、cp、volume、network。
無法沿用的功能較少,但差異更明顯。Swarm mode 沒有對等功能,因此 Swarm stack 無法部署。需要與 Docker socket 通訊的工具,必須匯出 Podman socket;部分工具仍會察覺兩者差異。Traefik 的 Docker provider 指向 /run/user/<uid>/podman/podman.sock 後可以運作,但 Watchtower 完全沒有對應位置,因為 podman auto-update 已負責相同工作。儲存空間彼此獨立,因此 Podman 看不到先前使用 Docker 拉取的映像,而忙碌中的 Docker 主機上的 podman images 一開始會是空的。
執行中的 stack 遷移,逐步進行
- 建立或選擇擁有容器的非特權使用者,並確認該使用者在
/etc/subuid中具有範圍。 - 使用完整名稱重新拉取所有來自 registry 的映像檔。Podman 使用自己的映像檔儲存區,不會讀取 Docker 的儲存區。
- 使用
docker save app:1.4 | podman load將本機建置的映像檔移轉過去。 - 停止 Docker 容器,將每個 volume 的內容從
/var/lib/docker/volumes/<name>/_data複製出來,再使用podman unshare chown -R 1000:1000 <path>修正擁有權。 - 處理連接埠設定:在反向代理後方發布 1024 以上的連接埠,或設定
net.ipv4.ip_unprivileged_port_start。 - 每個容器建立一個 quadlet 檔案,執行
systemctl --user daemon-reload,再啟動各項服務。 - 執行
sudo loginctl enable-linger <user>,重新啟動 VPS,重新登入,並確認podman ps再次列出所有服務。
兩個引擎不共用任何資源:映像檔儲存區與網路彼此分離。因此,遷移期間可以同時執行兩者,唯一可能互相衝突的是主機連接埠號碼。先移轉一項服務,監控一天,再移轉下一項。
Podman 與 Docker:哪一個適合放在你的 VPS 上?
如果你的堆疊使用由其他人共同維護的 compose 檔案,或依賴會連線到 Docker socket 的工具,請繼續使用 Docker。與其他人撰寫的設定保持相容,本身就是一項實際功能,而 Docker 在這方面支援更多。如果團隊成員的筆記型電腦都執行 Docker,則在正式環境使用相同的引擎也能帶來實際效益。
如果 VPS 只執行少量由你完整管理的服務,或希望每個應用程式都使用獨立的非特權使用者,且主機上完全不需要 docker 群組,請改用 Podman。發行版的整合情況也很重要:RHEL 及其重建版本將 Podman 作為支援的引擎,因此在這些系統上使用 Podman,較少遇到意外。如果你仍想在這些主機上使用 Docker,Rocky Linux 和 AlmaLinux 上的 dnf 安裝方式會先移除目前擁有 docker 命令的 podman-docker wrapper。如果你已經使用 systemd unit 管理其他所有服務,quadlet 會像是補上缺少的一環,而不是需要重新學習的新工具。
還有一種中間方案值得說明。Rootful Podman 的行為與 Docker 相近,透過 wrapper 保留 docker 命令,同時不再需要常駐 daemon。它也放棄了 rootless 模式,而這正是會改變安全性狀況的部分,因此應將它視為過渡方案。
如果你仍在建置第一台容器主機,在全新 VPS 上設定並強化 Docker 的流程會是較短的路徑,而且先前學到的內容都不會白費。兩種引擎使用相同的映像檔與 volume,因此日後遷移時,主要改變的是服務的管理方式,其他部分幾乎不變。
FAQ
Podman 是 Docker 的直接替代品嗎?
就您輸入的命令而言,兩者相當接近。安裝 podman-docker 會提供 /usr/bin/docker wrapper,而 run、ps、build、logs 和 exec 的行為也相同。但它不是 daemon 的替代品。Swarm 沒有對應功能。連線至 /var/run/docker.sock 的工具必須改為使用每位使用者的 Podman socket。Docker 拉取的映像也不會出現在 Podman 中,因為兩者使用不同的儲存空間。
為什麼我以 rootless Podman 執行的容器會在登出 SSH 後停止?
因為 systemd 會在您的最後一個登入工作階段結束時停止使用者工作階段及其中的所有使用者服務。執行 sudo loginctl enable-linger <user>,然後確認 loginctl show-user <user> --property=Linger 的輸出為 Linger=yes。Linger 會讓該使用者的 systemd 執行個體在沒有作用中工作階段時繼續執行。這也是容器能在重新開機後再次啟動的原因。
為什麼我的 volume 中的檔案擁有者是 UID 100999?
Rootless Podman 會將容器 UID 0 對應至您的主機使用者,再將容器 UID 1 以上的 UID 對應至您的 subuid 範圍。若範圍從 100000 開始,容器 UID 1000 在主機上就會變成 100999。請使用 podman unshare chown 1000:1000 /path/to/data 從 namespace 內修正,第一次執行時使用 :U flag 掛載,或使用 --userns=keep-id 讓容器 UID 與您自己的 UID 相符。
我可以繼續搭配 Podman 使用 docker-compose.yml 嗎?
可以,有兩種方式。podman-compose 會讀取該檔案,並直接操作 Podman CLI。或者使用 systemctl --user enable --now podman.socket 啟用相容性 socket,設定 DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock,再對該 socket 執行真正的 docker compose。使用 network_mode: host 時可能會遇到相容性問題;掛載 Docker socket 的服務也可能有問題。restart: always 則需要 quadlet unit 和 linger,才能在重新開機後繼續執行。
Rootless 真的能讓容器更安全嗎?
它會消除一項特定風險:從 rootless 容器逃逸的程序,取得的是您非特權使用者的權限,而不是 root 的權限。這項保護很有價值,也是 root 等效的 docker 群組在 rootless Podman 下沒有對應功能的原因。但它無法阻止 kernel 漏洞,也無法保護您自己的使用者可以讀取的檔案。因此,對任何伺服器都會採取的其他強化措施仍應保留。