Rocky Linux 與 AlmaLinux 安裝 Docker 教學
在 Rocky Linux 或 AlmaLinux 安裝 Docker Engine,並解決 Podman 佔用 docker 指令與 SELinux 掛載權限衝突等常見問題,確保容器服務穩定運行。
在 Rocky Linux 與 AlmaLinux 上安裝 Docker
若要在 Rocky Linux 或 AlmaLinux 上安裝 Docker,請加入 Docker 官方的 dnf 儲存庫,安裝引擎與 compose 外掛程式,接著啟用服務。此過程僅需四道指令,且由於兩者皆為 Red Hat Enterprise Linux (RHEL) 的重製版並共享相同的套件架構,因此在兩者上的操作完全相同。CentOS Stream 的運作方式亦同。以下內容適用於上述所有發行版,若您仍在兩者間猶豫,決定因素在於各專案所承諾的相容性,以及您的舊款 CPU 是否仍受支援。
安裝過程簡短,因此本指南大部分篇幅將說明 Enterprise Linux (EL) 與 Ubuntu 的差異。Podman 可能已佔用您映像檔中的 docker 指令。SELinux 會阻擋未標記正確標籤的 bind-mounted 檔案。Firewalld 不會過濾 Docker 發布的連接埠,因此容器連接埠可能已對網際網路開放,而 firewall-cmd 卻顯示無任何連接埠開啟。
請勿使用 get.docker.com 提供的 Docker 便利腳本。Docker 官方文件建議不要在生產環境中使用。該腳本會未經詢問即重寫您的儲存庫設定,且無法安全地重新執行以進行升級。手動加入儲存庫意味著 dnf upgrade 會將 Docker 視為系統中其他套件來管理。這也將引擎納入 dnf-automatic 的管理範圍(若您已設定定時套用安全性更新),因此請儘早決定是否要讓 Docker 自動修補,或保留至維護視窗期間處理。無論採取何種方式,升級皆會替換已封裝的二進位檔,而舊的 dockerd 仍會持續執行,此時 needs-restarting 指令可協助您找出哪些服務仍在執行剛被替換的程式碼。
Is podman already answering the docker command?
Rocky Linux and AlmaLinux ship podman in their default repositories, and many VPS images install it for you. Some images go further and install podman-docker, which places a shell script at /usr/bin/docker that calls podman. Every docker command you type then runs podman instead, so a guide written for Docker starts producing output you did not expect.
The first tell is a banner. The /usr/bin/docker script checks for the file /etc/containers/nodocker, and when that file is missing it prints one line before it runs anything:
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.Someone may have created that file to silence the banner, so do not rely on it alone. Ask the package database which package owns the binary:
command -v docker
rpm -qf "$(command -v docker)"An answer starting with podman-docker means podman is answering. An answer starting with docker-ce-cli means real Docker. If rpm -qf reports that no package owns the file, someone installed it by hand and you should read the script before trusting it.
Podman runs the same OCI images and is a reasonable choice. If you want it, stop here. Both are Linux container engines, so if the platform decision is still open, it is worth knowing that FreeBSD jails isolate a full userland instead of running layered images pulled from a registry. If you want Docker Engine, remove the conflicting packages first. This is the list Docker documents for RHEL:
sudo dnf remove docker docker-client docker-client-latest docker-common \
docker-latest docker-latest-logrotate docker-logrotate docker-engine podman runcRead what dnf plans to take with it before you confirm. On a fresh VPS image the list is short. On a box someone has already used, removing podman can pull out cockpit-podman or another tool that depends on it.
Keeping podman alongside Docker is possible in principle: remove only podman-docker, so the docker name is free, and runc, which the containerd.io package replaces. Docker's documentation treats podman as a conflicting package, so this layout is not one Docker supports. If the install still reports a conflict, use the full removal list above.
使用 dnf config-manager 新增 Docker 套件庫
Docker 於 download.docker.com 發布適用於 Enterprise Linux 的 RPM 套件。該套件庫檔案指向 CentOS 樹狀目錄,這也是 Rocky Linux 與 AlmaLinux 所參照的來源。將 Rocky 系統指向 CentOS 套件庫看似錯誤,但了解 Red Hat 於 2020 年將 CentOS 轉為 Stream 版本後,這兩個發行版如何從 CentOS 血脈衍生而出 後便能理解。截至 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.repodnf 第 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 會從您的 release 套件中展開此變數。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。若您的 release 設定將 $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,即 dockerd。docker-ce-cli 是您輸入的 docker 指令。containerd.io 是 daemon 所驅動的容器執行時期。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-worldDocker 的 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 Engine 運作正常時卻缺失:
docker compose version正常的輸出應如 Docker Compose version v2.x.x 所示。重新開機後服務是否恢復,與啟用 daemon 是兩回事,請參閱 restart policies 決定 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_t 的 scontext= 欄位,以及一個命名為您剛才在目錄上看到的標籤之 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 開放 SSH 與網頁連接埠,因為下述狀況必須在擁有可供比對的區域規則集時才具備參考價值。現在,請發布一個連接埠並查看 firewalld 的狀態:
sudo docker run -d --name web -p 8080:80 nginx:alpine
sudo firewall-cmd --list-portsfirewall-cmd 會輸出空行。從另一台機器執行 curl -I http://YOUR_SERVER_IP:8080/ 會得到 HTTP/1.1 200 OK。此時連接埠已對網際網路開放,但防火牆卻顯示無任何規則。
問題根源在於封包的傳輸路徑。Firewalld 的區域規則僅過濾發送給主機本身的流量。發布的連接埠並非發送給主機:Docker 會安裝一條目的位址轉換(Destination 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 會為您保留一個鏈(chain)。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 映像檔會使用如 enp1s0 或 ens3 等名稱。在 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-worldusermod -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 行以確認展開結果。若 dnf 在擷取中繼資料時出現 Status code: 404,代表變數展開為點發行版本(point release),此時請編輯 /etc/yum.repos.d/docker-ce.repo 並改為僅使用主要版本號即可修復。
Docker 與 podman 可以安裝在同一台伺服器上嗎?
Docker 文件將 podman 與 runc 列為衝突套件,並建議在安裝 Docker Engine 前移除兩者。實際衝突點在於 podman-docker 套件,它擁有 /usr/bin/docker 並將所有 docker 指令導向至 podman。執行 rpm -qf "$(command -v docker)" 可查看該路徑的所屬套件。若輸出以 podman-docker 開頭,代表回應的是 podman。Docker 不支援同時保留這兩種引擎,因此在正式環境中,請擇一使用。
為什麼我的容器在 bind mount 時會出現 permission denied?
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 會在處理其自身的 accept 規則前先執行該鏈。
將使用者加入 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 模式,這屬於獨立的安裝路徑,而非單純的設定調整。