SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor ・更新日 2026-09-04

Rocky LinuxとAlmaLinuxにDockerをインストール

Rocky LinuxとAlmaLinuxへDocker Engineをdnfで導入する手順です。podmanがdockerコマンドを占有する問題と、SELinuxがバインドマウントを拒否する場合の対処も説明します。

Rocky Linux と AlmaLinux に Docker をインストールする

Rocky Linux または AlmaLinux に Docker をインストールするには、Docker の公式 dnf リポジトリを追加し、Compose プラグイン付きでエンジンをインストールしてから、サービスを有効化します。この作業は 4 つのコマンドで完了します。両ディストリビューションは Red Hat Enterprise Linux (RHEL) の再ビルドであり、パッケージ構成を共有しているため、手順は同一です。CentOS Stream でも同じ手順を使用できます。以下は両方に適用されます。どちらを選ぶかまだ決めていない場合は、各プロジェクトが示す互換性の方針と、使用中の古い CPU が引き続きサポートされるかどうかが判断材料になります

インストール手順は短いため、このガイドの大部分では Enterprise Linux (EL) と Ubuntu の違いを説明します。イメージによっては、Podman がすでに docker コマンドを提供している場合があります。SELinux は、バインドマウントしたファイルに正しいラベルが付いていないとアクセスをブロックします。Firewalld は Docker が公開するポートをフィルタリングしないため、firewall-cmd では何も開いていないと表示されても、コンテナのポートがインターネットに公開されることがあります。

get.docker.com の Docker convenience script は使用しないでください。Docker の公式ドキュメントでも、本番環境での使用は推奨されていません。このスクリプトは確認なしにリポジトリ設定を書き換え、安全に再実行してアップグレードすることもできません。リポジトリを手動で追加すれば、dnf upgrade はホスト上の他のパッケージと同じように Docker を扱います。これにより、セキュリティ更新をタイマーで適用する dnf-automatic を使用している場合は、その対象にエンジンも含められます。そのため、Docker を自動的に更新するか、メンテナンス時間まで更新を保留するかを早めに決めてください。どちらの場合でも、アップグレードによってパッケージ化されたバイナリは置き換わりますが、古い dockerd は動作し続けます。どのサービスが置き換えたばかりのコードを引き続き実行しているかは、needs-restarting コマンドで確認できます

podman が docker コマンドを処理していないか確認する

Rocky Linux と AlmaLinux では、デフォルトのリポジトリから podman をインストールできます。多くの VPS イメージでは、あらかじめ podman がインストールされています。さらに、一部のイメージでは podman-docker までインストールされます。これにより、podman を呼び出すシェルスクリプトが /usr/bin/docker に配置されます。その後に入力した docker コマンドはすべて podman で実行されるため、Docker 向けに書かれた手順では、想定外の出力が表示されます。

最初の手掛かりはバナーです。/usr/bin/docker スクリプトは /etc/containers/nodocker ファイルの有無を確認します。このファイルがない場合、何かを実行する前に次の 1 行を表示します。

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 がファイルを所有するパッケージはないと報告する場合は、誰かが手動でインストールしています。信頼する前に、そのスクリプトを確認してください。

Podman は同じ OCI イメージを実行でき、十分に実用的な選択肢です。Podman を使用する場合は、ここで終了してください。どちらも Linux コンテナエンジンですが、プラットフォームの選択がまだ決まっていない場合は、FreeBSD jail はレジストリから取得したレイヤー化イメージを実行するのではなく、完全なユーザーランドを分離するという違いも把握しておくとよいでしょう。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 の名前を空け、containerd.io パッケージが置き換える runc も削除します。ただし、Docker のドキュメントでは podman を競合パッケージとして扱っているため、この構成は Docker がサポートする構成ではありません。インストール時に競合が報告される場合は、上記の完全な削除一覧を使用してください。

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.repo

dnf の Version 5 では --add-repo 引数が削除されたため、2 つ目のコマンドは新しいリリースで失敗します。使用中のバージョンを確認し、対応する形式を選択してください。

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 に同じファイルが書き込まれます。誤った形式を使用すると、何も通知されずに誤動作するのではなく、unknown-argument エラーで失敗します。そのため、問題を見落とすことはありません。

このリポジトリファイルでは、baseurl$releasever を含むパスに設定されています。dnf はこの変数をリリースパッケージから展開します。Rocky Linux と AlmaLinux では、この変数がメジャーバージョン番号に設定されます。そのため、EL 9 では 9、EL 10 では 10 となり、Rocky のサーバーで CentOS リポジトリが正しく解決されます。インストール前に展開結果を確認してください。

sudo dnf repoinfo docker-ce-stable

Repo-baseurl の行を確認します。末尾は /9/x86_64/stable または /10/x86_64/stable になっているはずです。使用中のリリースで $releasever9.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

5 つのパッケージがあり、それぞれ役割が異なります。docker-ce はデーモン、dockerd です。docker-ce-cli は入力する docker コマンドです。containerd.io はデーモンが操作するコンテナランタイムです。docker-buildx-plugin はイメージをビルドします。docker-compose-plugindocker compose をサブコマンドとして提供します。

これらのパッケージは、ハイフン付きの docker-compose バイナリをインストールしません。これは Compose v1 の形式で、2023 年 7 月にサポート終了となりました。docker-compose をハイフン付きで呼び出す設定やスクリプトは、スペース付きの docker compose に更新する必要があります。

最初のインストールでは、Docker の署名キーをインポートする前に処理が停止し、フィンガープリントが表示されます。このキーは、直前に追加したリポジトリファイルの gpgkey=https://download.docker.com/linux/centos/gpg から取得されます。受け入れる前に、dnf が表示したフィンガープリントとその URL の内容を比較してください。

頻繁に発生するエラーが 1 つあります。dnf が containerd.io には container-selinux が必要だが、それを提供するパッケージがないと報告した場合、AppStream リポジトリが無効になっています。dnf repolist を実行し、appstream が一覧に含まれていることを確認してください。EL 9 および EL 10 では、container-selinux はこのリポジトリからリリースされます。

Docker を起動して実行状態を確認する

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

Docker の RPM パッケージは、インストール後もデーモンを停止状態かつ無効状態のままにします。そのため、この手順は Docker の CentOS ページにはありますが、deb がサービスを自動起動する Ubuntu ページにはありません。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 group セクションで解決できます。

compose plugin は別のパッケージであり、エンジンが正常でも不足している場合があるため、個別に確認します。

docker compose version

正常な結果は Docker Compose version v2.x.x のようになります。再起動後にサービスを復旧できるかどうかは、デーモンを有効化することとは別の問題です。restart policy によって Compose サービスがブート時に復旧するかどうかが決まります

bind mount で permission denied が発生するのはなぜですか?

Rocky Linux と AlmaLinux では、デフォルトで SELinux (Security-Enhanced Linux) が enforcing モードで動作します。getenforce で確認できます。このコマンドは Enforcing を出力します。

Docker コンテナは SELinux の container_t タイプで実行されます。このタイプで読み書きできるのは、container_file_t のラベルが付いたファイルだけです。ホストで作成したディレクトリには、親パスから継承したラベルが付きます。そのラベルは container_file_t ではありません。ホスト側から見ると所有者、グループ、モードが正しくても、コンテナからのアクセスは拒否されます。次の 3 つのコマンドで再現できます。

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

原因は 2 つのコマンドで確認できます。ls -ldZ /srv/site はラベルを出力します。/srv 以下のパスでは、そのラベルは system_u:object_r:var_t:s0 であり、container_file_t ではありません。次に sudo ausearch -m avc -ts recent はカーネルの監査記録を出力します。この記録には avc: denied { read }scontext= フィールド、container_t を示すフィールド、そしてディレクトリで確認したラベルを示す tcontext= フィールドが含まれます。この 2 つのフィールドの不一致が原因です。

修正するには、volume 引数に suffix を付けます。Docker がパスのラベルを変更します。

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

小文字の :z は、コンテンツを shared として再ラベル付けします。そのため、複数のコンテナで同じディレクトリを使用できます。大文字の :Z は、コンテンツを private かつ unshared として再ラベル付けし、1 つのコンテナ専用にします。その場合、同じパスを読み取る 2 つ目のコンテナは拒否されます。sidecar や backup コンテナもアクセスする場合は :z を使用します。1 つのコンテナだけが所有するデータベースディレクトリには :Z を使用します。

Docker のドキュメントには、繰り返し注意すべき警告があります。再ラベル付けは再帰的に行われるためです。/home/usr のようなシステムディレクトリを :Z 付きで bind mount すると、「ホストマシンが動作不能になり、ホストマシンのファイルを手動で再ラベル付けする必要が生じる場合があります」。これらの suffix は、コンテナ用に作成したディレクトリにだけ指定してください。システムパスには指定しないでください。

Compose では、同じ文字列に suffix を付けます。

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

注意すべき制限が 2 つあります。--mount flag では SELinux ラベルを設定できません。ラベルが必要な場合は -v を使用してください。named volume には suffix は必要ありません。Docker が /var/lib/docker/volumes 以下に作成するディレクトリへ、自動的にラベルを付けるためです。

SELinux を無効にしないでください。sudo setenforce 0 は 1 分間のテストに限って使用します。これでコンテナが動作するなら、問題はラベルにあり、解決策は :z です。すぐに sudo setenforce 1 で元に戻してください。Enterprise Linux では、bind mount の permission denied には、コンテナ内部からは同じに見える別々の原因が 2 つあります。1 つは SELinux ラベルです。もう 1 つは、通常の数値によるユーザーとグループの所有権です。これは PUID と PGID の変数が解決するために存在する問題です。ls -lnZ を使うと、モード、数値の所有者、ラベルを 1 行で確認できます。どちらが原因かを切り分けられます。

firewalld では閉じているように見える公開ポートに接続できるのはなぜか?

Firewalld は Rocky Linux と AlmaLinux のデフォルトファイアウォールです。sudo systemctl is-active firewalld で実行中か確認します。このホストでまだ設定していない場合は、まず firewalld で SSH と Web ポートを開く 必要があります。以下の現象は、比較対象となる有効なゾーンルールセットがあって初めて意味を持つためです。次にポートを公開し、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(ネットワークアドレス変換)ルールを設定し、パケットがホストの input 経路に到達する前に宛先をコンテナのアドレスへ書き換えます。そのため、カーネルはパケットをローカルに配信せず、転送します。さらに Docker は、ブリッジインターフェースを docker という firewalld ゾーンに配置します。このゾーンの target は ACCEPT です。また、任意のゾーンから docker ゾーンへの転送を許可する docker-forwarding という forwarding policy を追加します。ゾーンルールはこのパケットを認識できません。

最も簡単な対策は、ファイアウォールルールを追加しないことです。公開時のホスト側アドレスを 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/

ローカルでの curlHTTP/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 unit に記述してください。

2025 年にリリースされた Docker Engine 28.0 では、隣接する別の抜け道も修正されました。公開されていないコンテナポートへの直接ルーティングアクセスが、DOCKER チェーンでブロックされるようになりました。この変更は公開ポートには影響しないため、現在のバージョンでも上記の説明はそのまま当てはまります。運用上、習慣にしておくべきことが 1 つあります。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 を編集します。ただし、現在のシェルには変更前のグループ一覧が保持されているため、新しいシェルを開始するまで変更は反映されません。newgrp docker はグループを適用したシェルを開始するため、すぐにテストできます。新しい SSH セッションでは、自動的に変更が反映されます。

このグループが付与する権限を明確に理解してください。グループに所属すると /var/run/docker.sock への書き込み権限が付与されます。このソケットと通信できるものは、ホストのファイルシステムをマウントしたコンテナをデーモンに起動させることができます。次のコマンドで、その意味を確認できます。

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

これは、root だけが読み取れるファイルを、sudo 権限のないアカウントから読み取ります。Docker のインストール後の公式ドキュメントにも同じ説明があり、docker グループは root と同等の権限を付与します。アカウントをこのグループに追加するのは、そのアカウントに sudo も付与する場合だけにしてください。新しいサーバーでアカウントを設定する場合は、後から決めるのではなく、VPS での最小権限ユーザー設定の一環として検討してください。

Docker には、権限のないユーザーとしてデーモンを実行する rootless モードもあります。これは別のインストール手順であり、ストレージドライバーと 1024 未満のポートの動作が変わります。後から追加するフラグではなく、独立したプロジェクトとして計画してください。

次に進む内容

ここまでで、エンジン、compose plugin、再起動後も稼働するサービス、そして上記で説明した EL 固有の 3 つの動作を確認できました。次はサービスごとの compose.yaml です。Compose ファイルの構成では、ファイル形式とそれを操作するコマンドを説明します。コンテナホストを初めて構築する場合は、VPS で Docker を実行するで、このガイドでは扱っていないサイジング、ストレージ、イメージの衛生管理に関する事項を確認できます。

FAQ

Docker の CentOS リポジトリは Rocky Linux と AlmaLinux で動作しますか?

はい。https://download.docker.com/linux/centos/docker-ce.repodnf config-manager とともに追加します。そのファイル内の 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 が表示される場合、変数がポイントリリースに展開されています。/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 でコンテナが permission denied になるのはなぜですか?

Rocky Linux と AlmaLinux では、デフォルトで SELinux が enforcing になっています。コンテナは container_t 型で実行され、container_file_t のラベルが付いたファイルにしかアクセスできません。そのため、作成したディレクトリに誤ったラベルが付いていると、所有者やモードに関係なくアクセスが拒否されます。ホスト側のパスに対して ls -ldZ を実行し、sudo ausearch -m avc -ts recent で確認します。後者は、異なる2つのコンテキストを含む avc: denied を出力します。コンテナ間で共有するコンテンツには、ボリューム引数に :z を追加します。1つのコンテナだけで使用するコンテンツには :Z を追加します。:Z/home または /usr に対して実行しないでください。ラベル変更は再帰的に適用され、ホストを壊します。

コンテナポートを公開するために firewalld でポートを開く必要がありますか?

いいえ。それが問題の原因です。Docker の NAT ルールは、パケットがホストの input パスに到達する前に宛先アドレスを書き換えます。そのため、firewalld のゾーンルールはパケットを検査しません。Docker はブリッジを docker という firewalld のゾーンに配置し、target を ACCEPT にも設定します。-p 8080:80 で起動したコンテナはインターネットから到達可能ですが、sudo firewall-cmd --list-ports は何も出力しません。ホストからだけサービスに到達させる場合は、-p 127.0.0.1:8080:80 で特定のアドレスに公開します。または、DOCKER-USER chain にフィルタリングルールを追加します。この chain は、Docker が独自の accept ルールを処理する前に処理されます。

ユーザーを docker group に追加しても安全ですか?

root 権限を与えることになります。docker group のメンバーは /var/run/docker.sock に書き込めます。その後、docker run --rm -v /:/host alpine wc -l /host/etc/shadow を使えば、sudo 権限のないアカウントから root 専用ファイルを読み取れます。Docker のインストール後のドキュメントにも、同じ権限関係が記載されています。すでに sudo の使用を許可できるアカウントだけを追加し、共有アカウントやサービスアカウントでは sudo docker を使い続けてください。非特権ユーザーでコンテナを実行する必要がある場合は、rootless mode が代替手段です。これは設定ではなく、別のインストール経路です。