前言
Docker,是一個作為容器化管理且讓人耳熟能詳的技術;相較於Podman,此為後起的容器化管理且由RedHat等大廠支持開發與發展的技術,並作為下個世代(Next-generation)的容器管理引擎。
兩者在使用上,在指令方面,針對容器化管理幾乎是無縫接軌;也就是說,多數的指令所接的大部分參數皆彼此相容。因此,對於Docker使用者能夠較為順利地轉移至Podman。
在本文章中,作者將展示相異的地方,並針對不同的地方講解與該如何設定;此外,也展示當虛擬機器或主機上,如何使用Podman技術達到執行以多個容器所組成服務的效果。
前置作業
以下為本文章的前置作業與條件:
- 具有一台虛擬主機(需要為基於KVM或Hyper-V等虛擬化技術)或實體主機,且此主機已經安裝好Docker。
- Docker安裝方式是以apt指令並搭配Docker官方提供外部的套件庫。
- 此虛擬主機或實體主機需要為Ubuntu 24.04。
- 此虛擬主機或實體主機具有git指令能夠使用。
移除Docker
在開始安裝Podman前,建議是移除Docker引擎與相關的套件,原因是Docker預設是以root使用者來執行docker指令;而Podman則是以非root使用者(rootless)來執行指令,兩者在存取「DOCKER_HOST」環境變數路徑的時候,會因為權限不同而有所衝突,以下為未移除Docker且安裝Podman時,執行podman指令所發生的情況:
# 確認docker指令已經存在且可用 $ which docker /usr/bin/docker # 執行podman compose ps指令,前提為:需要在有docker-compose.yml檔案作執行 $ podman compose ps failed to connect to the docker API at unix:///run/user/1000/podman/podman.sock; check if the path is correct and if the daemon is running: dial unix /run/user/1000/podman/podman.sock: connect: no such file or directory Error: executing /usr/libexec/docker/cli-plugins/docker-compose ps: exit status 1
從前述的錯誤訊息,此為Docker存取路徑權限與使用者透過Podman存取作法不同所導致。因此建議在安裝Podman前,執行下列指令將Docker相關的套件做移除:
sudo apt purge -y docker-ce docker-ce-cli containerd.io sudo apt autoremove -y
接著,將Docker相關的套件移除後,再次執行「podman compose ps」指令,則不會出現讀取的路徑有權限問題了。
安裝Podman
首先,最重要的步驟為安裝Podman,預設的情形下,Ubuntu 24.04已經搭載Podman套件,且版本為「4.9.x」;因此,我們可以透過下列的指令來完成Podman安裝:
sudo apt update sudo apt install -y podman
由於後續的教學與操作步驟會需要用到compose的動作,即使用多個容器來描述服務;因此,我們還需要安裝「podman-compose」套件並作為類似「docker compose」指令之動作。
執行安裝前述的套件指令如下:
sudo apt install -y podman-compose # 確定podman.socket順利於背景運行 $ sudo systemctl status podman.socket ● podman.socket - Podman API Socket Loaded: loaded (/usr/lib/systemd/system/podman.socket; enabled; preset: enabled) Active: active (listening) since Wed 2026-07-08 18:32:11 UTC; 18min ago Triggers: ● podman.service Docs: man:podman-system-service(1) Listen: /run/podman/podman.sock (Stream) CGroup: /system.slice/podman.socket Jul 08 18:32:11 nifi.peterli.website systemd[1]: Listening on podman.socket - Podman API Socket.
兩者指令皆安裝完成所需要的套件後,再執行下列的指令,將本文使用的「yt-dlp-api」專案範例,使用git指令複製一份到本地的虛擬主機或實體主機:
git clone https://github.com/peter279k/yt-dlp-api cd yt-dlp-api
此專案已經為了相容於Podman,做了許多相對應的調整。調整的設定與項目如下:
- 每個「Dockerfile」中,定義的「FROM」需要明確指定映像檔來源,有別於Docker指令會使用「docker.io/library/」,Podman預設是要指定確切的映像檔案路徑。
- 「podman compose」指令已經做過相容性,預設會讀取「Dockerfile」作為指定建置的容器描述檔案,所以專案中的「Dockerfile」檔案名稱能夠保持不變。
- 需要注意的是,設定曝露埠號(port number)需要是1024以上的埠號,原因是Podman預設是以非root使用者來執行podman相關的指令。
接著,再執行「podman compose build」開始建置此yt-dlp-api的多容器所組成的服務,執行過程之部分截圖如下:
$ podman compose build >>>> Executing external compose provider "/usr/bin/podman-compose". Please refer to the documentation for details. <<<< podman-compose version: 1.0.6 ['podman', '--version', ''] using podman version: 4.9.3 podman build -f ./Dockerfile -t yt-dlp-api_worker . STEP 1/9: FROM docker.io/library/python:3.12-slim Trying to pull docker.io/library/python:3.12-slim... Getting image source signatures Copying blob b32430367bf0 done | Copying blob aff2d9f8dc87 done | Copying blob e95a6c7ea7d4 done | Copying blob df79d931cd67 done | Copying config 9cbfea8426 done | ...... COMMIT yt-dlp-api_yt-dlp-api --> ab4637096ca7 Successfully tagged localhost/yt-dlp-api_yt-dlp-api:latest Successfully tagged localhost/yt-dlp-api_worker:latest ab4637096ca77dba3fcc350689bb1197ea20f2e9d50f4f07556f398db6c77680 exit code: 0
執行完成後,會每個建置的容器都帶有「exit code: 0」,若有出現非0的離開狀態碼的情況,則需要特別去注意失敗原因並找到問題作修復。
建置完成後,執行「podman compose up -d」將此服務以背景的方式運行,執行後所輸出的指令如下:
>>>> Executing external compose provider "/usr/bin/podman-compose". Please refer to the documentation for details. <<<<
podman-compose version: 1.0.6
['podman', '--version', '']
using podman version: 4.9.3
** excluding: set()
['podman', 'inspect', '-t', 'image', '-f', '{{.Id}}', 'yt-dlp-api_worker']
['podman', 'inspect', '-t', 'image', '-f', '{{.Id}}', 'yt-dlp-api_yt-dlp-api']
['podman', 'ps', '--filter', 'label=io.podman.compose.project=yt-dlp-api', '-a', '--format', '{{ index .Labels "io.podman.compose.config-hash"}}']
podman volume inspect yt-dlp-api_shared-download || podman volume create yt-dlp-api_shared-download
['podman', 'volume', 'inspect', 'yt-dlp-api_shared-download']
......
Getting image source signatures
Copying blob fcb6fd84b2a0 done |
Copying blob 1b30016634d5 done |
Copying blob df68ee7e7a00 done |
Copying blob acf093e7a04f done |
Copying blob e95a6c7ea7d4 skipped: already exists
Copying blob cd9307c9ecd8 done |
Copying blob 1645c1e06f46 done |
Copying config 9f33606b36 done |
Writing manifest to image destination
b93d1bcbda077f3e0c938752ec63cf70122da251ed7eab81e3c08064f5c54026
exit code: 0
執行完成後,我們可以使用「podman compose ps」來檢視執行起來的服務所包含的容器是否都有順利的運作,預期執行指令後得到的結果如下:
podman compose ps >>>> Executing external compose provider "/usr/bin/podman-compose". Please refer to the documentation for details. <<<< podman-compose version: 1.0.6 ['podman', '--version', ''] using podman version: 4.9.3 podman ps -a --filter label=io.podman.compose.project=yt-dlp-api CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES bf298dce930d docker.io/library/alpine:latest chown -R 1000:100... 7 minutes ago Exited (0) 7 minutes ago yt-dlp-api_init-permissions_1 109cf59dd608 docker.io/library/rabbitmq:3-management rabbitmq-server 6 minutes ago Up 6 minutes 0.0.0.0:5672->5672/tcp rabbitmq 0ebc395686d7 localhost/yt-dlp-api_worker:latest celery -A app.wor... 6 minutes ago Up 6 minutes celery_worker 0a7831039e03 localhost/yt-dlp-api_yt-dlp-api:latest gunicorn -w 4 --b... 6 minutes ago Up 6 minutes 0.0.0.0:8000->8000/tcp yt-dlp-api_yt-dlp-api_1 b93d1bcbda07 docker.io/library/nginx:latest nginx -g daemon o... 6 minutes ago Up 6 minutes 0.0.0.0:8080->80/tcp yt-dlp-api_nginx_1 exit code: 0
當確定與完成以Podman建置服務後,將主機重新開機,則會發現先前使用「podman compose up -d」指令所運行的服務不會隨著開機自動啟動。這是與Docker不同的地方,Docker有Daemon的概念,因此當運行的容器在重新開機後,仍會透過背景運行的Docker Daemon (Engine)將容器逐一的運行;有別於Podman,Podman為Daemon-less特性,因此在運行容器再重新開機後,並不會隨著開機而自動運行。要解決Podman這樣的情形並讓yt-dlp-api服務能夠自動於開機運行,我們可以透過systemd來完成。
System Daemon我們能夠定義systemd service檔案,描述執行一次的背景服務並將指定的目錄下基於多容器所建置出的服務運行起來,systemd服務設定檔案內容如下:
# 先將確切的使用者啟用永久性的User Session,這樣一來能夠在使用者未登入情況下,定義於使用者底下的服務也能夠隨著開機自動啟動 sudo loginctl enable-linger $USER # 目錄不存在,則建立設定使用者的service目錄 mkdir -p ~/.config/systemd/user # 編輯service設定檔案 vim ~/.config/systemd/user/yt-dlp-api.service # 設定service檔案內容如下: [Unit] Description=Podman Compose Application Service After=network-online.target Wants=network-online.target [Service] Type=oneshot RemainAfterExit=true # 設定工作目錄為放置compose.yml或docker-compose.yml檔案的路徑 WorkingDirectory=/home/peter/yt-dlp-api ExecStart=/usr/bin/podman compose up -d ExecStop=/usr/bin/podman compose down [Install] WantedBy=default.target
從設定的服務設定檔案中,我們可以知道此服務類型為「oneshot」,表示開機後,當網路連線成功後,執行一次性的服務。
當設定完成後,分別依序執行下列的指令設定、啟用與執行我們設定的服務:
# 切換至yt-dlp-api專案並執行podman compose down # 確認此服務已經沒有容器在運行,避免與前述systemd service執行起來的容器相衝突 cd ~/yt-dlp-api podman compose down # 以此使用者執行systemd並啟用與馬上執行yt-dlp-api之systemd服務設定檔案 systemctl --user enable --now yt-dlp-api.service # 執行完成後,可以在yt-dlp-api專案下執行podman compose ps指令來檢查個容器運行的狀態 podman compose ps >>>> Executing external compose provider "/usr/bin/podman-compose". Please refer to the documentation for details. <<<< podman-compose version: 1.0.6 ['podman', '--version', ''] using podman version: 4.9.3 podman ps -a --filter label=io.podman.compose.project=yt-dlp-api CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 43341fe0347f docker.io/library/alpine:latest chown -R 1000:100... About a minute ago Exited (0) About a minute ago yt-dlp-api_init-permissions_1 aa3e7192eb80 docker.io/library/rabbitmq:3-management rabbitmq-server About a minute ago Up About a minute 0.0.0.0:5672->5672/tcp rabbitmq df5737c3b1a5 localhost/yt-dlp-api_worker:latest celery -A app.wor... About a minute ago Up About a minute celery_worker 413209675e5b localhost/yt-dlp-api_yt-dlp-api:latest gunicorn -w 4 --b... About a minute ago Up About a minute 0.0.0.0:8000->8000/tcp yt-dlp-api_yt-dlp-api_1 71685bededff docker.io/library/nginx:latest nginx -g daemon o... About a minute ago Up About a minute 0.0.0.0:8080->80/tcp yt-dlp-api_nginx_1 exit code: 0 # docker.io/library/alpine:latest之容器狀態為「Exited」是正常的,因為此容器為修改目錄權限後即離開(exit 0)
接著,為了驗證此systemd服務設定檔有無作用,我們再次將主機重新開機,並待主機完成開機後,再切換至「yt-dlp-api」專案底下,執行「podman compose ps」來檢查個容器運行的狀態。
當機於多容器所定義的yt-dlp-api服務能夠在主機重新開機後,順利地自行運作起來,則表示先前所設定開機執行一次性的systemd服務設定檔案發揮應有的行為與效果。
結語
本文章,以「yt-dlp-api」專案當作範例,去展示如何從Docker用法遷移至Podman,並將不同的地方,透過其他設定來完成Docker既有的特性。
Podman雖然有些特性與Dockr相異,但是Podman仍有不少優勢,例如:
- Podman內建具有將指定的容器產生出供Kubernetes部署的YML檔案,例如:podman generate kube指令。
- Podman本身具有將產生出來的Kubernetes部署YAML檔案做模擬部署動作來驗證YAML檔案之準確性,例如:podman play kube指令。
- 好處是提早練習Kubernetes且能夠增加遷移至Kubernetes之速度。
參考資料
- https://developers.redhat.com/e-books/podman-action
- https://github.com/peter279k/yt-dlp-api
- https://zedas.fr/posts/start-a-podman-container-at-system-boot
- https://oneuptime.com/blog/post/2026-03-02-how-to-configure-systemd-logind-for-session-management-on-ubuntu
- https://www.it-hure.de/2024/02/podman-compose-and-systemd