VPSでPodmanとDockerは何が違う?
Podmanはデーモンなし、rootlessが標準です。VPS運用でのcompose、quadlet、自動起動、1024未満のポート、ボリューム所有権の違いを具体的に解説します。
Podman と Docker で実際に異なる点
Podman と Docker は、VPS 上で同じ OCI (open container initiative) イメージを実行するため、どのソフトウェアを実行できるかが選択の基準になるわけではありません。違いはプロセスモデルにあります。Docker はすべてのコンテナを管理する root デーモンを実行し、docker コマンドはそのデーモンに処理を依頼する小さなクライアントです。Podman にデーモンはありません。podman run は、呼び出し元のプロセスの子プロセスとして、権限のない自分のユーザーでコンテナを起動します。
その他の違いは、すべてこの点から生じます。自動起動はデーモンではなく systemd の役割になります。ボリュームの所有権は user namespace を経由するため、ホスト上で ls -l によって確認できる所有者と、コンテナから見える所有者は異なります。1024 未満のポートは、カーネル設定を変更するまで bind に失敗します。docker CLI (command line interface) はラッパー経由で引き続き使用できますが、Docker socket を必要とする処理では動作しません。
デーモンなし: コンテナの起動時に実際に実行されるもの
Docker ホストでは、pstree -a に root として dockerd が表示され、その横に containerd が表示され、実行中のコンテナごとに containerd-shim-runc-v2 が 1 つ表示されます。アプリケーションはその shim の子プロセスであり、shim は PID 1 の子プロセスです。コンテナと、コンテナを起動したシェルを接続するものはありません。デーモンを停止すると、そのホスト上のすべてのコンテナに対する制御プレーンを失います。デフォルトの live-restore 設定が無効な場合は、systemctl restart docker によるコンテナの再起動も失われます。
Podman には同等のプロセスがありません。コンテナを起動すると、コンテナのメインプロセスを保持する conmon (コンテナモニター) プロセスが 1 つ作成されます。このプロセスは、コマンドを実行したユーザーが所有します。
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 では、conmon が root ではなくログインユーザーとして実行されていることを確認できます。curl では 200 が出力されます。コンテナを所有する中央サービスがないため、sudo apt upgrade podman は実行中のコンテナを停止しません。また、1 つのコンテナのモニターがクラッシュしても、ほかのコンテナまで停止することはありません。
ただし、デーモンがないことには代償もあります。再起動後にコンテナを起動するものがありません。Docker の --restart=always は、ブート時にデーモンが維持する仕組みです。Podman ではこれを systemd に置き換えます。以下の quadlet セクションでは、この方法を説明します。
ソケットも重要な要素です。/var/run/docker.sock は root が所有する API (application programming interface) エンドポイントです。このソケットに書き込めるプロセスは、ホストのファイルシステムをマウントする特権コンテナを起動できます。ユーザーを docker グループに追加すると、より時間のかかる経路でそのユーザーに root 権限を与えることになります。これは、各サービスアカウントに必要なアクセス権だけを付与する という説明と併せて確認してください。Podman は要求しない限りソケットを公開しません。ソケットを作成した場合も、/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 を提供します。これらは、一般ユーザーが subordinate ID の範囲を取得するための setuid ヘルパーです。これらがないと rootless コンテナは起動しません。podman info の出力は rootless: true になるはずです。
2026 年 8 月時点で、Ubuntu 24.04 は Podman 4.9、Debian 13 は Podman 5.x を提供します。この差は重要です。quadlet ファイルには 4.4 以降が必要で、.pod quadlet ファイルには 5.0 が必要だからです。upstream のドキュメントから例をコピーする前に podman --version を実行してください。
rootless で使用する各ユーザーには、subordinate ID の範囲が必要です。
grep "$USER" /etc/subuid /etc/subgidUbuntu で 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初回実行時には、もう 1 つ注意点があります。Podman は Docker Hub を自動的に使用しません。短いイメージ名は /etc/containers/registries.conf 内の unqualified-search-registries に対して解決されます。ターミナルに接続されていないスクリプトでは、pull が short-name resolution enforced but cannot prompt without a TTY で失敗します。毎回、完全な名前を指定してください。nginx ではなく docker.io/library/nginx:1.27 を使用します。
レンタルサーバーで rootless コンテナが実際にもたらすもの
rootless コンテナは user namespace 内で実行されます。user namespace は、プロセスに独自のユーザー ID マップを与えるカーネル機能です。namespace 内では、コンテナのスーパーユーザーは UID (user ID) 0 です。namespace 外、つまり VPS 上では、同じプロセスが通常のログインユーザーとして扱われます。コンテナ内の root は、ホスト上の root ではありません。
これが実際の効果の範囲です。root での実行を要求するイメージ、リモートコード実行の脆弱性がある Web アプリケーション、外部で UID 0 であることに依存するコンテナエスケープが発生しても、最終的に得られるのはマシン全体の権限ではなく、非特権ユーザーの権限です。rootless はカーネルのバグから保護するものではありません。また、ユーザー自身のファイルも保護しません。エスケープしたプロセスはあなたのユーザーとして実行され、あなたが読み取れるものはすべて読み取れるためです。
Docker も rootless で実行できます。dockerd-rootless-setuptool.sh install はユーザーごとのデーモンを設定し、正常に動作します。違いは、デフォルトの方向性です。Podman では、明示的に指定しなくても rootless で実行されます。そのため、最初に遭遇する問題は、80 番ポートをバインドできないコンテナになります。サービスが 2 年間、気付かないまま root として実行され続ける事態を避けられます。
ボリューム内のファイルが UID 100999 の所有になるのはなぜですか?
これは同じ user namespace が原因です。コンテナの UID 0 はホストのユーザー UID にマッピングされます。コンテナの UID 1 は、subuid の範囲における最初の ID にマッピングされ、その後も順に増えていきます。100000 から始まる範囲では、コンテナの UID 1000 はホスト上の UID 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"コンテナでは 1000 と表示されます。ホスト上の一覧では所有者が 100999 と表示されます。100000 に 1000 を加えて 1 を引くと 100999 になるためです。問題が発生しているわけではありません。また、単純な chown でも解決しません。unprivileged user は、namespace の外部ではファイルの所有権を変更できないためです。
解決方法は 4 つあります。
podman unshare chown 1000:1000 "$PWD/data"は、同じ user namespace 内で chown を実行します。そこでの番号は、コンテナ内で本来の意味を持ちます。-v "$PWD/data:/data:U"は、ソースディレクトリの所有権を Podman に修正させます。重要なデータではなく、新規作成したディレクトリで使用してください。--userns=keep-idは、ホストの UID をコンテナ内でも同じ UID にマッピングします。これにより、新しく作成されるファイルは自分が所有する状態になります。-v appdata:/dataのような名前付きボリュームを使用すると、Podman が自分のストレージ内に正しい所有権で作成するため、この問題を避けられます。
Docker でこの問題に悩んだことがある場合は、1 つ上の層で発生している同じ問題です。多くのイメージが公開している PUID と PGID の変数 は、コンテナ内のプロセスが使用する UID を設定します。rootless Podman では、その UID がさらにもう一度マッピングされます。rootless コンテナ内の PUID=1000 も、ホスト上では 100999 が所有するファイルを書き込みます。この 2 回目のマッピングを考慮して番号を選ぶか、データを名前付きボリュームに移して、この問題を気にしない構成にしてください。
マウントについて、もう 2 点あります。Fedora と RHEL の例で見かける :z および :Z のフラグは、SELinux の再ラベルオプションです。Ubuntu は AppArmor を使用するため、これらのフラグは機能しません。rootless Podman では、ユーザーが読み取れないホストディレクトリもマウントできません。これは不具合ではなく、意図された動作です。
Why does rootless Podman refuse to publish port 80?
Because binding a port below 1024 needs a privilege your user does not have. The error names the fix:
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 deniedTwo answers work. Lower the threshold for the whole host:
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_startThe last command should echo 80 back. Be clear about what that setting does: every user on the machine can now bind 80 and 443, not only the one running containers. On a single-admin VPS that is an acceptable trade. On a box carrying other people's accounts it is not. The other answer is to publish on 8080 and put a reverse proxy in front, which is where you want certificates issued and renewed by certbot on nginx anyway.
Rootless publishing also changes what your application sees. Podman 4.x uses slirp4netns with the rootlesskit port handler by default, and forwarded connections arrive with a rewritten source address, so the access log records every visitor as 10.0.2.100. Podman 5.0 changed the default to pasta, which keeps the real client address. On 4.x, --network slirp4netns:port_handler=slirp4netns restores the true source address at some cost in throughput.
There is one good surprise here. A rootless published port is an ordinary listening socket owned by a normal process, so your firewall's input rules apply to it. Docker publishes ports by writing NAT (network address translation) rules plus its own forwarding accepts, which is exactly why a published Docker port ignores the ufw rule you thought was blocking it. Rootful Podman uses similar plumbing and inherits the same trap. Rootless does not.
Podman でも Docker Compose ファイルは動作しますか?
主に 2 つの方法があります。1 つ目は podman-compose です。これは、同じファイルを読み込み、Podman CLI を実行する別実装です。
sudo apt install -y podman-compose
podman-compose up -d
podman ps2 つ目は、実際の Docker Compose から、ユーザーごとのソケット経由で 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 には同じコンテナが表示されるはずです。コンテナは 1 組しかないためです。名前解決も機能します。Podman のデフォルトネットワークバックエンドである netavark は aardvark-dns を実行するため、ユーザー定義ネットワーク上のコンテナは名前で相互に検出できます。
制約もあります。/var/run/docker.sock をマウントするものは、Podman ソケットを参照するように設定するか、削除する必要があります。network_mode: host はユーザーネームスペース内で動作が異なります。condition: service_healthy を使用する depends_on は、podman-compose のバージョンによって対応状況にばらつきがあります。restart: always は、それだけでは再起動後に維持されません。次のセクションでこの問題を解決します。Compose は 複数コンテナのスタックを 1 つのファイルで記述する方法として引き続き有用であり、Podman では変換レイヤーとして機能します。数年間維持する予定のスタックでは、quadlet に変換し、2 つの抽象化を併用せず 1 つだけ管理してください。
Pod に関する考え方:Docker に答えがないもの
Pod は、1 つの network namespace を共有するコンテナのグループです。Podman は、その namespace を保持するための小さな infra コンテナを起動します。各メンバーは、ユーザー定義ネットワークやサービスディスカバリを使わず、127.0.0.1 で相互に接続できます。
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 では、infra コンテナを含む 3 つのコンテナで構成された pod Running が表示されます。web コンテナは、app-cache:6379 ではなく 127.0.0.1:6379 で Redis に接続します。共有 namespace には、2 つのルールがあります。ポートはメンバーではなく pod で公開します。また、2 つのメンバーが同じポートで待ち受けることはできません。
これは Kubernetes のモデルであり、Podman はこのモデルを重視しています。podman kube generate app > app.yaml は、実行中の構成から Kubernetes manifest を生成します。古いパッケージでは podman generate kube という表記になっています。podman kube play app.yaml は、その manifest を別のホストで再作成します。Quadlet には、そのようなファイルを systemd サービスとして実行する .kube unit type があります。これはサービスをグループ化する方法として明確に異なり、将来 Kubernetes を利用する可能性が少しでもある場合に Podman を選ぶ最大の理由です。
デーモンなしで自動起動する: quadlet unit
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セクションヘッダーが volume を作成するため、~/.config/containers/systemd/caddy-data.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 は enable できず、systemd は Failed to enable unit: Unit /run/user/1000/systemd/generator/caddy.service is transient or generated. と返します。[Install] セクションがブート時にコンテナを起動し、daemon-reload がファイル編集後に unit を再生成します。
次に、ほとんどの人が見落とす設定です。
sudo loginctl enable-linger deploy
loginctl show-user deploy --property=LingerLinger=yes を実行してください。linger を有効にしないと、最後の SSH 接続を閉じた時点で systemd が user session 全体を終了します。そのため、rootless container もすべて停止し、ブート時にも再起動しません。ログアウトすると消えるコンテナは、必ずこの設定が原因です。
コンテナは通常の service unit のメインプロセスとして動作するため、systemd 自身の制御がそのまま適用されます。[Service] セクションの MemoryMax= と CPUQuota= は、systemd で制御する他の service とまったく同じように動作します。これは cgroup v2 (control group version 2) が必要です。Ubuntu では 22.04 以降、デフォルトで cgroup v2 が使用されています。podman info | grep -i cgroup で確認してください。
更新にも対応する仕組みがあります。AutoUpdate=registry と systemctl --user enable --now podman-auto-update.timer を指定すると、同じ tag の新しい image が registry にあるか確認し、unit を再起動します。新しいコンテナの起動に失敗した場合は、以前の image にロールバックします。変更内容は、最初に podman auto-update --dry-run で確認してください。従来の podman generate systemd command も残っていますが、deprecated です。新しく作成するものには quadlet を使用してください。
docker alias が適用される範囲と、適用されない範囲
sudo apt install -y podman-docker
sudo touch /etc/containers/nodocker
docker pspodman-docker は、Podman を呼び出す /usr/bin/docker ラッパーをインストールします。nodocker ファイルがない場合、呼び出すたびに最初に Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg. と表示されます。このラッパーは、日常的に入力する次のコマンドを対象にします。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 で既に pull したイメージを参照することはできません。また、負荷の高い Docker host 上の podman images は、最初は空です。
稼働中のスタックを段階的に移行する
- コンテナの所有者となる非特権ユーザーを作成または選択し、
/etc/subuidにユーザー範囲があることを確認します。 - レジストリから取得したものは、完全修飾名を使用して再取得します。Podman には独自のイメージストアがあり、Docker のイメージストアは読み取りません。
- ローカルでビルドしたイメージは、
docker save app:1.4 | podman loadで移行します。 - Docker コンテナを停止し、各ボリュームの内容を
/var/lib/docker/volumes/<name>/_dataからコピーして、podman unshare chown -R 1000:1000 <path>で所有権を修正します。 - ポートの扱いを決めます。リバースプロキシの背後で 1024 より大きいポートを公開するか、
net.ipv4.ip_unprivileged_port_startを設定します。 - コンテナごとに quadlet ファイルを 1 つ作成し、
systemctl --user daemon-reloadを実行して各サービスを起動します。 sudo loginctl enable-linger <user>を実行し、VPS を再起動して再度ログインします。その後、podman psにすべてのサービスが再び一覧表示されることを確認します。
2 つのエンジンは何も共有しません。イメージストレージとネットワークは別々です。そのため、移行中は両方を実行できます。競合する可能性があるのは、ホストのポート番号だけです。1 つのサービスを移行して 1 日監視してから、次のサービスを移行します。
Podman と Docker: VPS にはどちらを導入すべきか
スタックが他の人も保守する compose ファイルで構成されている場合、または Docker socket と通信するツールに依存している場合は、Docker を使い続けてください。他の人が作成する構成との互換性は実用的な利点であり、その点では Docker の方が優れています。チームの開発者端末がすべて Docker を実行している場合は、本番環境でも同じ engine を実行することで、具体的な利点が得られます。
VPS で実行するサービスが少数で、すべてを自分で管理している場合は、Podman への切り替えを検討してください。また、各アプリケーションを専用の unprivileged user で実行し、システム上に docker group を一切作成したくない場合にも適しています。ディストリビューションとの整合性も重要です。RHEL とその rebuild では、サポート対象の engine として Podman が提供されているため、それらのシステムでは Podman の方が予期しない問題が少なくなります。ほかのすべてを systemd unit で管理している場合は、quadlet を新しいツールとして学ぶというより、欠けていた機能が追加されたように感じるでしょう。
中間的な選択肢もあります。rootful Podman は Docker とよく似た動作をし、wrapper 経由で docker command を維持しながら、常時稼働する daemon をなくせます。ただし、rootless の利点は失われます。rootless はセキュリティ上の立場を変える要素なので、rootful Podman は移行途中の選択肢として扱ってください。
初めて container host を構築している場合は、新しい VPS で Docker をセットアップして hardening する手順の方が短い道のりです。そこで得た知識が無駄になることもありません。両方の engine で images と volumes は同じオブジェクトなので、後から移行しても変わるのはサービスの管理方法が中心で、それ以外はほとんど変わりません。
FAQ
Podman は Docker のそのまま置き換えになりますか?
入力するコマンドについては、ほぼ置き換えられます。podman-docker をインストールすると /usr/bin/docker ラッパーが提供され、run、ps、build、logs、exec も同じように動作します。ただし、daemon の置き換えではありません。Swarm に相当する機能はなく、/var/run/docker.sock に接続するツールは、ユーザー単位の Podman socket を参照するよう変更する必要があります。また、Docker で pull したイメージは、Docker と Podman が別々のストレージを使用するため、Podman からは見えません。
SSH からログアウトすると rootless Podman コンテナが停止するのはなぜですか?
最後のログインセッションを閉じると systemd がユーザーセッションと、その配下のすべてのユーザーサービスを停止するためです。sudo loginctl enable-linger <user> を実行し、loginctl show-user <user> --property=Linger が Linger=yes を出力することを確認してください。Linger を有効にすると、アクティブなセッションがなくても、そのユーザーの systemd instance が実行され続けます。これにより、再起動後にもコンテナが起動します。
ボリューム内のファイルが UID 100999 で所有されるのはなぜですか?
rootless Podman は、コンテナの UID 0 をホストのユーザーに割り当て、その後のコンテナ UID 1 以降を subuid の範囲に割り当てます。100000 から始まる範囲では、コンテナの UID 1000 はホスト上で 100999 になります。podman unshare chown 1000:1000 /path/to/data を使って namespace 内から修正するか、初回起動時に :U flag を指定して mount するか、--userns=keep-id を使ってコンテナの UID を自分の UID に合わせてください。
Podman でも docker-compose.yml を使い続けられますか?
はい。方法は 2 つあります。podman-compose はファイルを読み込み、Podman CLI を直接操作します。別の方法では、systemctl --user enable --now podman.socket で互換 socket を有効にし、DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock を設定して、実際の docker compose をその socket に対して実行します。network_mode: host、Docker socket を mount するサービス、そして再起動後も動作させるために quadlet unit と linger が必要な restart: always では、互換性の問題が発生する可能性があります。
rootless にすると、本当にコンテナの安全性は向上しますか?
1 つのリスクを除去できます。rootless コンテナから脱出したプロセスが取得するのは root の権限ではなく、権限のないユーザーの権限です。これは有効な対策です。そのため、root と同等の docker group に相当するものは、rootless Podman にはありません。ただし、kernel の脆弱性を防ぐことはできません。また、自分のユーザーが読み取れるファイルも保護されません。そのため、通常のサーバーと同じ hardening を引き続き実施してください。