SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-13

Rocky Linux 與 AlmaLinux 安裝 Docker 教學

在 Rocky Linux 與 AlmaLinux 安裝 Docker Engine。本指南說明如何解決 Podman 指令衝突、處理 SELinux 掛載權限問題,以及避免 Firewalld 導致的連接埠外洩風險。

在 Rocky Linux 與 AlmaLinux 上安裝 Docker

若要在 Rocky Linux 或 AlmaLinux 上安裝 Docker,請加入 Docker 官方的 dnf 儲存庫,安裝包含 compose 外掛程式的引擎,並啟用該服務。此過程僅需四道指令,且由於兩者皆為 Red Hat Enterprise Linux (RHEL) 的重製版並共用相同的套件架構,因此安裝步驟完全相同。CentOS Stream 的操作方式亦同。

安裝過程相當簡短,因此本指南大部分內容將涵蓋 Enterprise Linux (EL) 與 Ubuntu 之間的差異。Podman 可能已佔用您映像檔中的 docker 指令。SELinux 會阻擋未標記正確標籤的 bind-mounted 檔案。Firewalld 不會過濾 Docker 發布的連接埠,因此容器連接埠可能會直接暴露於網際網路,而 firewall-cmd 卻顯示沒有任何連接埠開放。

請勿使用來自 get.docker.com 的 Docker 簡易安裝腳本。Docker 官方文件指出不建議在生產環境中使用該腳本。它會未經詢問即改寫您的儲存庫設定,且無法安全地重新執行以進行升級。手動加入儲存庫能確保 dnf upgrade 將 Docker 視為系統中其他標準套件進行管理。

Podman 是否已經在回應 docker 指令?

Rocky Linux 與 AlmaLinux 的預設儲存庫中皆包含 podman,許多 VPS 映像檔也會預先安裝。部分映像檔甚至會進一步安裝 podman-docker,該套件會在 /usr/bin/docker 放置一個呼叫 podman 的 shell script。這會導致您輸入的每個 docker 指令實際上都在執行 podman,進而使原本針對 Docker 編寫的指南產生預期外的輸出。

最明顯的跡象是啟動橫幅。/usr/bin/docker script 會檢查 /etc/containers/nodocker 檔案是否存在;若檔案遺失,它會在執行任何動作前先輸出一行訊息:

Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.

由於有人可能會建立該檔案以隱藏橫幅,請勿僅依賴此跡象。請查詢套件資料庫以確認該二進位檔屬於哪個套件:

command -v docker
rpm -qf "$(command -v docker)"

若回應以 podman-docker 開頭,代表目前由 podman 回應。若以 docker-ce-cli 開頭,則為真正的 Docker。若 rpm -qf 顯示該檔案不屬於任何套件,代表它是手動安裝的,您應在信任該 script 前先閱讀其內容。

Podman 執行相同的 OCI 映像檔,是一個合理的選擇。若您想使用它,請在此停止。若您需要 Docker Engine,請先移除衝突的套件。以下是 Docker 針對 RHEL 所列出的清單:

sudo dnf remove docker docker-client docker-client-latest docker-common \
  docker-latest docker-latest-logrotate docker-logrotate docker-engine podman runc

在確認前,請先閱讀 dnf 預計移除的套件清單。在全新的 VPS 映像檔上,該清單通常很短。但在已使用過的伺服器上,移除 podman 可能會連帶移除 cockpit-podman 或其他依賴它的工具。

原則上,Podman 與 Docker 可以並存:僅移除 podman-docker 以釋放 docker 名稱,並移除 runc(該檔案會被 containerd.io 套件取代)。由於 Docker 的官方文件將 podman 視為衝突套件,因此 Docker 並不支援此配置。若安裝時仍回報衝突,請使用上述的完整移除清單。

使用 dnf config-manager 新增 Docker 套件庫

Docker 於 download.docker.com 發布適用於 Enterprise Linux 的 RPM 套件。該套件庫檔案指向 CentOS 樹狀目錄,這也是 Rocky Linux 與 AlmaLinux 所參照的來源。截至 2026 年 8 月,Docker 針對 CentOS Stream 9 與 CentOS Stream 10 均採用此套件庫。

sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

dnf 第 5 版移除了 --add-repo 參數,因此在較新的發行版上執行第二個指令會失敗。請先確認您安裝的版本,再選擇對應的指令格式:

dnf --version

若輸出顯示為 5.x 版本,請改用子指令格式:

sudo dnf config-manager addrepo --from-repofile=https://download.docker.com/linux/centos/docker-ce.repo

兩者皆會將相同的檔案寫入 /etc/yum.repos.d/docker-ce.repo。若使用錯誤的格式,系統會回報未知參數錯誤,而非在無聲無息中執行錯誤操作,因此您不會錯過此錯誤。

該套件庫檔案將 baseurl 設定為包含 $releasever 的路徑,dnf 會從您的發行版套件中展開此變數。Rocky Linux 與 AlmaLinux 會將其設為主要版本號,例如 EL 9 為 9,EL 10 為 10,這就是為何 CentOS 套件庫能在 Rocky 系統上正確解析的原因。安裝前請先確認展開結果:

sudo dnf repoinfo docker-ce-stable

請閱讀 Repo-baseurl 行。其結尾應為 /9/x86_64/stable/10/x86_64/stable。若您的發行版將 $releasever 設定為點版本(例如 9.6),dnf 在擷取元資料時會回報該 URL 為 Status code: 404。請編輯 /etc/yum.repos.d/docker-ce.repo 並將 $releasever 取代為純主要版本號以修正此問題。

安裝引擎與 compose 外掛

sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

此處包含五個套件,各司其職。docker-ce 是 daemon,即 dockerddocker-ce-cli 是您輸入的 docker 指令。containerd.io 是 daemon 所驅動的容器執行時期(container runtime)。docker-buildx-plugin 用於建置映像檔。docker-compose-plugin 則提供 docker compose 作為子指令。

這些套件不會安裝帶有連字號的 docker-compose 二進位檔。那是 Compose v1,已於 2023 年 7 月終止支援。任何呼叫 docker-compose(帶連字號)的指令都需要更新為 docker compose(帶空格)。

首次安裝時會暫停以匯入 Docker 的簽章金鑰,並顯示其指紋。該金鑰來自您剛新增的儲存庫檔案中的 gpgkey=https://download.docker.com/linux/centos/gpg,因此在接受之前,請先比對 dnf 顯示的指紋與該 URL 是否相符。

有一種常見的失敗狀況。若 dnf 回報 containerd.io 需要 container-selinux 且無任何套件提供,代表您的 AppStream 儲存庫已停用。請執行 dnf repolist 並確認 appstream 已列出,因為這正是 container-selinux 在 EL 9 與 EL 10 上的來源。

啟動 Docker 並確認其執行狀態

sudo systemctl enable --now docker
sudo systemctl status docker --no-pager
sudo docker run --rm hello-world

Docker 的 RPM 套件在安裝後,預設不會啟動 daemon 且不會設定為開機自動執行。這就是為什麼此步驟出現在 Docker 的 CentOS 頁面,而非 Ubuntu 頁面(Ubuntu 的 deb 套件會自動啟動服務)。若跳過 enable,Docker 僅會在當次開機期間執行,重開機後服務將保持關閉,導致所有容器無法運作。

systemctl status 應顯示 Active: active (running)hello-world 容器應輸出 This message shows that your installation appears to be working correctly. 後結束。若出現關於 /var/run/docker.sock 的權限錯誤,表示您遺漏了 sudo,請參閱下方的 docker 群組章節進行修正。

請分開檢查 compose 外掛程式,因為它是獨立的套件,可能在引擎運作正常時仍未安裝:

docker compose version

正常的輸出應為 Docker Compose version v2.x.x。重開機後服務是否自動恢復,與啟用 daemon 是兩回事,請參閱 重啟策略決定 Compose 服務是否於開機時自動啟動

為什麼 bind mount 會出現 permission denied?

Rocky Linux 與 AlmaLinux 預設會在 enforcing 模式下執行 SELinux (Security-Enhanced Linux)。請使用 getenforce 確認,該指令會輸出 Enforcing

Docker 容器在 SELinux 類型 container_t 下執行,該類型僅能讀寫標記為 container_file_t 的檔案。您在主機上建立的目錄會繼承其父路徑的標籤,而該標籤並非 container_file_t。即使從主機端查看擁有者、群組與權限模式皆正確,容器仍會被拒絕存取。執行以下三行指令即可重現此問題:

sudo mkdir -p /srv/site
echo hello | sudo tee /srv/site/index.html
sudo docker run --rm -v /srv/site:/usr/share/nginx/html:ro nginx:alpine cat /usr/share/nginx/html/index.html

容器會輸出:

cat: can't open '/usr/share/nginx/html/index.html': Permission denied

透過兩行指令即可找出原因。ls -ldZ /srv/site 會顯示標籤,對於 /srv 下的路徑而言,其標籤為 system_u:object_r:var_t:s0 而非 container_file_t。接著,sudo ausearch -m avc -ts recent 會顯示核心的稽核紀錄,其中包含 avc: denied { read }、一個命名為 container_tscontext= 欄位,以及一個命名為您剛才在目錄上看到的標籤之 tcontext= 欄位。這兩個欄位之間的不匹配即為問題所在。

解決方法是在 volume 引數後加上後綴。Docker 會自動為您重新標記該路徑:

sudo docker run --rm -v /srv/site:/usr/share/nginx/html:ro,z nginx:alpine cat /usr/share/nginx/html/index.html

小寫的 :z 會將內容重新標記為共享,以便多個容器能使用同一個目錄。大寫的 :Z 則將其重新標記為私有且不共享,僅綁定於單一容器;若第二個容器嘗試讀取相同路徑,將會被拒絕。若該路徑會被 sidecar 或備份容器存取,請使用 :z。若該目錄為單一容器所擁有的資料庫目錄,請使用 :Z

Docker 文件中有一項值得重申的警告:重新標記是遞迴進行的。若使用 :Z/home/usr 等系統目錄進行 bind mount,「將導致主機無法運作,且您可能需要手動重新標記主機檔案」。請僅將這些後綴用於您為容器建立的目錄,切勿用於系統路徑。

在 Compose 中,後綴需加在同一個字串上:

services:
  web:
    image: nginx:alpine
    volumes:
      - /srv/site:/usr/share/nginx/html:ro,z

有兩個限制容易被忽略。--mount 旗標無法設定任何 SELinux 標籤,因此若有需求,請改用 -v。命名卷(Named volumes)不需要後綴,因為 Docker 會自行處理 /var/lib/docker/volumes 下所建立目錄的標籤。

請勿關閉 SELinux。僅將 sudo setenforce 0 用於一分鐘的測試:若容器隨後能正常運作,則問題在於標籤,此時 :z 才是解決方案。測試後請務必立即使用 sudo setenforce 1 將其恢復。在 Enterprise Linux 上,bind mount 出現 permission denied 的原因有二,且從容器內部看來兩者完全相同。其一是 SELinux 標籤,其二是常見的數值使用者與群組擁有權,這正是 PUID 與 PGID 變數存在的目的ls -lnZ 可在同一行顯示模式、數值擁有者與標籤,讓您能判斷當前處理的是哪種問題。

為何 firewalld 顯示連接埠已關閉,但發布的連接埠仍可連線?

Firewalld 是 Rocky Linux 與 AlmaLinux 的預設防火牆。請使用 sudo systemctl is-active firewalld 確認其執行狀態。現在發布一個連接埠,並查看 firewalld 認為哪些連接埠是開啟的:

sudo docker run -d --name web -p 8080:80 nginx:alpine
sudo firewall-cmd --list-ports

firewall-cmd 會輸出空行。從另一台機器執行 curl -I http://YOUR_SERVER_IP:8080/ 會回傳 HTTP/1.1 200 OK。該連接埠對網際網路開放,但您的防火牆卻顯示無任何開放連接埠。

問題在於封包的路徑。Firewalld 的區域規則僅過濾發送給主機本身的流量。發布的連接埠並非發送給主機:Docker 會安裝目的地 NAT(網路位址轉譯)規則,在封包到達主機的輸入路徑前,將目的地改寫為容器的位址,因此核心會轉發該封包,而非將其傳遞給本機。隨後,Docker 會將其橋接介面放入名為 docker 的 firewalld 區域(其目標為 ACCEPT),並新增名為 docker-forwarding 的轉發策略,允許從任何區域轉發至 docker 區域。您的區域規則因此無法偵測到該封包。

最乾淨的解決方案是不使用防火牆規則。將發布的主機端綁定至 loopback,並在其前方放置反向代理:

sudo docker rm -f web
sudo docker run -d --name web -p 127.0.0.1:8080:80 nginx:alpine
curl -I http://127.0.0.1:8080/

本機的 curl 會回傳 HTTP/1.1 200 OK,而從另一台機器發出的相同請求將無法連線。在 -p 參數中未指定主機位址的任何設定,都會發布在所有介面上,因此請將單純的 -p 8080:80 視為公開暴露該服務的決定。

當您確實需要讓服務僅對特定位址開放時,Docker 為您保留了一個鏈。DOCKER-USER 會在 Docker 自身的 accept 規則之前處理,因此您放置於此的規則在 Docker 重啟並重寫其鏈後依然有效:

sudo iptables -I DOCKER-USER -i enp1s0 ! -s 203.0.113.10 -j DROP
sudo iptables -S DOCKER-USER

請從 ip route show default 取得介面名稱,而非假設為 eth0,因為目前的 EL 映像檔會使用如 enp1s0ens3 等名稱。在 Rocky 與 AlmaLinux 上,iptables 指令是 nftables 的相容層,Docker 的鏈可透過此指令檢視。以此方式新增的規則在重啟後會消失,除非您儲存它們;因此,確認規則無誤後,請將其寫入 systemd 單元中。

2025 年發布的 Docker Engine 28.0 封堵了一個相關漏洞:現在已封鎖對未發布容器連接埠的直接路由存取,該限制位於 DOCKER 鏈中。此變更不影響已發布的連接埠,因此上述所有內容在當前版本中仍然適用。建議養成一個操作習慣:在執行任何 sudo firewall-cmd --reload 後,重新測試發布的連接埠。若連接埠停止回應,執行 sudo systemctl restart docker 即可重新安裝 Docker 的規則。

Ubuntu 管理員會透過不同的工具遇到相同的障礙,詳見 為何發布的 Docker 連接埠會忽略 ufw 規則。這兩種情況的原因皆為 NAT 路徑,僅有前方的防火牆工具不同。

將非 root 使用者加入 docker 群組

在每個 docker 指令前輸入 sudo 會變得繁瑣,而 docker 群組可免除此需求:

sudo usermod -aG docker $USER
newgrp docker
docker run --rm hello-world

usermod -aG 會編輯 /etc/group,但您目前的 shell 已載入群組列表,因此變更需在開啟新 shell 後才會生效。newgrp docker 可啟動一個已加入該群組的 shell,讓您立即進行測試。新的 SSH 連線會自動套用此變更。

請務必釐清該群組的授權範圍。成員資格賦予對 /var/run/docker.sock 的寫入權限,而任何能與該 socket 通訊的程式,都能要求 daemon 啟動一個掛載主機檔案系統的容器。以下指令展示了其含義:

docker run --rm -v /:/host alpine wc -l /host/etc/shadow

這能讓一個沒有 sudo 權限的帳號,讀取僅有 root 能存取的檔案。Docker 官方的安裝後說明文件也指出:docker 群組賦予的權限等同於 root。僅在您願意給予該帳號 sudo 權限時,才將其加入該群組。若您正在設定新伺服器的帳號,請將此決策納入 VPS 最小權限使用者設定 的規劃中,而非事後才處理。

Docker 亦提供 rootless 模式,能以無特權使用者身分執行 daemon。這屬於獨立的安裝路徑,且會改變儲存驅動程式與 1024 以下連接埠的運作方式,因此請將其視為獨立的專案進行規劃,而非事後才添加的旗標。

後續步驟

您現在已具備引擎、compose 外掛程式、可隨開機自動啟動的服務,以及上述針對 EL 系統的三種特定行為。下一步是為每個服務建立 compose.yaml,而 Compose 檔案結構 涵蓋了檔案格式與驅動它的指令。若這是您首次架設容器主機,在 VPS 上執行 Docker 涵蓋了本指南未提及的資源配置、儲存空間與映像檔維護等問題。

FAQ

Docker 的 CentOS 套件庫適用於 Rocky Linux 和 AlmaLinux 嗎?

適用。請使用 dnf config-manager 新增 https://download.docker.com/linux/centos/docker-ce.repo。該檔案中的 baseurl 包含 $releasever,Rocky Linux 與 AlmaLinux 會將其展開為主要版本號,因此 EL 9 系統會解析至 CentOS 9 樹狀目錄,EL 10 系統則解析至 CentOS 10。請使用 sudo dnf repoinfo docker-ce-stable 確認展開結果,並檢查 Repo-baseurl 行。若 Status code: 404 在 dnf 擷取元資料時發生,代表變數展開為點版本(point release),此時請編輯 /etc/yum.repos.d/docker-ce.repo 並改為僅使用主要版本號即可解決。

Docker 和 podman 可以安裝在同一台伺服器上嗎?

Docker 文件將 podmanrunc 列為衝突套件,並建議在安裝 Docker Engine 前移除兩者。具體的衝突點在於 podman-docker 套件,它擁有 /usr/bin/docker,會將所有 docker 指令導向至 podman。執行 rpm -qf "$(command -v docker)" 可查看哪個套件擁有該路徑。若輸出以 podman-docker 開頭,則代表由 podman 處理。Docker 不支援同時保留這兩個引擎的配置,因此在正式伺服器上,請擇一使用。

為什麼我的容器在掛載綁定(bind mount)時出現權限被拒?

Rocky Linux 與 AlmaLinux 預設強制執行 SELinux。容器以 container_t 類型執行,僅能存取標記為 container_file_t 的檔案,因此您建立的目錄若標籤不符,即使擁有者與權限模式正確,存取仍會被拒。請在宿主機路徑執行 ls -ldZ 確認,並使用 sudo ausearch -m avc -ts recent,它會印出 avc: denied 顯示兩個不匹配的上下文。若要在容器間共享內容,請在 volume 參數中加入 :z;若為單一容器私有內容,則使用 :Z。切勿將 :Z 指向 /home/usr,因為重新標記(relabel)是遞迴執行的,會損壞宿主機系統。

我需要為了發布容器連接埠而在 firewalld 開放連接埠嗎?

不需要,這正是問題所在。Docker 的 NAT 規則會在封包到達宿主機輸入路徑前重寫目的位址,因此 firewalld 的區域規則不會檢查該封包。Docker 也會將其橋接介面置於名為 docker 的 firewalld 區域中,且目標為 ACCEPT。以 -p 8080:80 啟動的容器可從網際網路存取,而 sudo firewall-cmd --list-ports 卻不會顯示任何資訊。若僅限宿主機存取服務,請使用 -p 127.0.0.1:8080:80 發布至特定位址,或將過濾規則插入 DOCKER-USER 鏈中,Docker 會在處理其自身的接受規則前先執行此鏈。

將使用者加入 docker 群組安全嗎?

這等同於賦予 root 權限。docker 群組成員可寫入 /var/run/docker.sock,而 docker run --rm -v /:/host alpine wc -l /host/etc/shadow 隨後會從無 sudo 權限的帳號讀取僅限 root 存取的檔案。Docker 的安裝後文件也說明了此等效性。僅將您已信任且具備 sudo 權限的帳號加入該群組,並持續為共享或服務帳號使用 sudo docker。若需要在非特權使用者下執行容器,請使用 Rootless 模式,這屬於獨立的安裝路徑,而非單純的設定調整。