なぜこの記事を書くのか
私のした苦労をみなさんが回避できるように、もしくは私と同じ苦労をなさった皆さんに共感してもらうことで心がかるくなることを願います。
経緯
1.平和だったころ
私は半分趣味でDockerコンテナのなかでメールサーバを立てようと思い立ちました。
手持ちの環境として
ノートPC: windows環境
デスクトップPC: Ubuntu Server 24.04
ディスプレイ、キーボードは接続せず、ノートPCからのSSHで操作
となっていました。
それで、ChromeのAIモードでいろいろと質問しながら設定を行いました。
Dockerのインストール、dockerfile、docker-compose.ymlの作成などを行い、Dockerである程度正常に動くコンテナができました。
コンテナの状態はportainerで確認すればいいと考えていました。
作りこんだ点は、実用するためにセキュリティを高めようと、ファイアウォールで最低限のポートだけを許可する形にする、メールサーバは仮想ユーザ方式を採用しコンテナ内のみのユーザvmail(ID:5000)を作成して、メッセージデータの所有者にした点です。
また、メッセージデータのバックアップがしやすいようにホストのディレクトリをマウントしてメッセージデータを記録するようにしていました。
また、メッセージの送信や受信に関わるログ(postfixとdovecot、getmailのログ)はホストのファイルをマウントして、ホストで直接tailなどで確認できるようにしていました。
2.誘惑に負ける
コンテナの連続稼働が2週間を経過したころだったでしょうか、私は「毎回SSHでログインして状態を確認するのではなく、WEBで状態の確認と操作ができるといいな」と考えました。
ChromeのAIモードに訊いてみるとcockpitというパッケージがあることを知り、すぐにデスクトップPCにインストールしてみました。
色々な情報が確認、操作が出来て、私は「これはいいな。よし、採用」と考えました。
特にWeb画面でターミナルが表示されsudo docker psのようにコマンドが実行できるのが私には便利に感じました。
確かにいいパッケージだと今でも思います、cockpitは。
ただ、それに伴う誘惑に私は勝てませんでした。
cockpitはRed Hat社が中心となって開発されているそうですね。
それで、cockpitの拡張機能としてcockpit-podmanがあり、podmanによるコンテナはcockpitの中で確認できるのです。
一方dockerはcockpit側では対応していませんでした。
ChromeのAIモードに訊いてみました。
「podmanについて教えて。」
答えは次のようだったと思います。
・Dockerと同じcontainerd技術を使っています。
・dockerfileやdocker-compose.ymlがそのまま使えるほか、dockerのオプションがほぼそのままpodmanで使えるほど高い互換性があります。
・Rootless環境で動かすとセキュリティがDockerよりも高められます。
私にとって「よりセキュリティになる」ことはとてもいいことに感じました。そして、高い互換性があることは必要なコストがとても少ないことに感じました。
私はDockerからPodmanに乗り換えることを決断しました。
少ないコストで高いセキュリティが手に入ること、cockpitの機能拡張としてメンテナンスが簡単になることの誘惑に負けた瞬間でした。
3.苦労
3.1 ポートが開けない
私はDockerコンテナの起動をスクリプトにまとめていました。
それはDockerコンテナがIPアドレスを固定しない形で実行しているのですが、それによってファイアウォールのufwの設定が変わるのです。
ちなみにdockerはコンテナのポートがufwで遮断できない仕様になっていて、ufw-dockerを使ってコンテナ単位で遮断する必要があるのですが、ufw-dockerは各コンテナが起動した後に実行することになるのでコンテナ実行の手順が複雑になってしまうのです。
さて、コンテナ実行スクリプトのsudo dockerとなっている個所をpodmanに書き換え、実行するとすぐにPermission Deniedエラーになりました。
調べてみると「1024番未満のポートは一般ユーザは占有できない」とのLinuxの制限とのことで、解除することはできるのですが、セキュリティが低下すると書いてあります。
悩みました。
セキュリティの低下は許容したくないのですが、ポートを143->10143と変更することは考えたくありません。反対に1024番未満のポートを開放するようにした場合の危険性について考えました。
セキュリティ低下となる理由が意図せず一般ユーザが既存のサービスを偽装してしまうことだとした場合、それを無効化する方法はないだろうかと。
ファイアウォールのufwはrootユーザしか変更できないので、ファイアウォールを基本として遮断するようにして必要なもののみポートを開くようにすれば、問題ないと考えました。
対処するため、スクリプトに次のコードを追加しました。
if [ "$(cat /proc/sys/net/ipv4/ip_unprivileged_port_start)" -gt 25 ]; then
$SUDO sysctl -w net.ipv4.ip_unprivileged_port_start=25
fi
スクリプトの実行でエラーは出なくなりました。
3.2 コンテナ内のデーモンが止まってた
スクリプトでコンテナが起動されたので、次のステップとしてThunderbirdでの接続を試みました。
クライアントPCのThunderbirdでメールサーバに接続しようとすると全く接続できませんでした。
スクリプトがきちんと動いてコンテナが起動していたので私の頭には「ufwが何か悪さしているのかな?」と
sudo ufw disable
など無効化してみたりしましたが、まったく接続できません。
「コンテナが起動したんだから、ファイアウォールかクライアントPCのThunderbirdの設定を見直して、直せばいいはず」と見直し作業をしても何も問題がないことだけがわかるだけでした。
また、ホストPC上のメールに関するログを見ても特にエラーは出ていませんでした。
落ち着いたころに、「じゃあ、コンテナ内でのエラーログを見てみよう」と思い、
podman exec -it containername /bin/sh
として、ログを見てみるとpostfixもdovecotも起動直後にエラーが出て止まっているじゃないですか。
しかも、その原因は設定ファイルがpermissionで読めないからだと言ってます。
もう、「はあ?聞いてないよ」ですよ。
コンテナは外部と切り離して実行するはずなのに、コンテナを動かす環境がDockerからPodmanに変えたらコンテナ内に影響するのが信じられませんでした。
調べてみたら、確かに私が悪かったのです。
podmanのrootless環境(rootでないユーザで実行する状態)では実行ユーザがrootに読み替えてファイルをアクセスするそうです。
で、私はDockerコンテナを作りこむときに、コンテナ内で一般ユーザが乗っ取られてもrootは乗っ取られることはないだろうと設定ファイル類は
chown root:root ./*
chmod 600 ./*
とかにしていました。
そのため、rootlessのPodman環境では設定ファイル類が所有者がnobodyとなって、アクセスできなくなっていたのです。
まず、コンテナ内にマウントするファイルはホスト側ではrootではなく実行ユーザそのものが所有者になるよう設定を書き換える必要がありました。
chown user:user ./*
これでひとまず、コンテナが起動直後に終了することはなくなりました。
3.3 メールが届かない
コンテナが動いたのでThunderbirdがdovecotに接続できるようになりました。
getmailも動いていてdovecotにメールを流し込んでいるログも残るようになりました。
しかし、メールを取得できません。
dovecotのログを見ると、メッセージデータへアクセスする際にpermissionでエラーになっていました。
これも私が悪かったんです。
メッセージデータなどはユーザvmail(ID:5000)を所有者とするようになっていました。
ところが、コンテナ内で
ls -al
としてみるとよくわからないID(ID:6002とか)のユーザが所有者になっているのです。
PodmanのUID/GIDマッピングという仕組みでホストのUID、GIDがコンテナ内では別のUID、GIDに変換されてしまうのです。
対策をどうするべきか考えましたが、結局ホスト側で「コンテナ内でvmail(ID:5000)となるUID、GID」となるように変換したUID、GIDで書き換えることにしました。
VMAIL_UID="5000"
PSEUDO_UID="$(podman unshare awk -v org="$VMAIL_UID" '$1==1 {print $2 + org -1}' /proc/self/uid_map)"
sudo chown -R "${PSEUDO_UID}:${PSEUDO_UID}" ./*
これで、Thunderbirdで新着メールが確認できるようになりました。
3.4 ファイアウォールが通らない
メールが確認できるようになったので、連続運用テストを行おうとファイアウォールを有効にしてみたところ、接続が出来なくなりました。
これも私が悪かったんです。
DockerではコンテナのIPアドレスを指定してやる必要があります。
一方、PodmanではホストのIPアドレスを指定してやることになります。
HOSTIP="192.168.1.1"
CONTAINERIP="10.3.0.1"
LOCALAREA="192.168.1.0/24"
(docker) sudo ufw route allow from $LOCALAREA to $CONTAINERIP port 110 proto tcp
(podman) sudo ufw allow from $LOCALAREA to $HOSTIP port 110 proto tcp
コンテナのIPアドレスを指定する必要もなくなり、ufw-dockerも必要なくなったので、この修正は簡単でした。
3.5 よくわからないタイミングでコンテナが止まる
連続運用テストをしているときによくわからないタイミングでコンテナが止まっていました。
で再度起動して停止するタイミングを調べてみたんですが、cockpitで別のサーバを表示しようとしたときにcockpitの画面がチラチラしてcockpitの接続が切れることに気づきました。そのタイミングでコンテナが終了しています。
ChromeのAIモードで「ubuntuでpodmanのコンテナが急に終了するのはなぜですか。」と訊いてみると「podmanを実行したユーザがログアウトしたタイミングでコンテナは終了する」とのことだったのですが、SSHの接続を切るタイミングとは合致しないのでなぜなのかわからなかったんです。
しばらく悩んで理由がわかりました。というか想像できました。
cockpitは接続するタイミングで実行ユーザと同じユーザとそのパスワードを指定しますが、それはサーバへのログインとなっており、cockpitを表示している間はずっとユーザがログイン状態なのだと思われます。
そして、cockpitで別のサーバに接続するタイミングでユーザがログアウトすると考えられます。
考えてみれば今までSSHで接続するときに同時にcockpitでも見ていたのでSSHでログアウトしてもユーザはログインした状態が継続していたのだと思います。
ユーザがログアウトした後もコンテナを動かすためにはユーザをLingerに設定する必要があります。
sudo loginctl enable-linger "user"
4.これからどうなるかAIに訊いてみた
ここまでいろいろとエラーが出てきたのでイライラしていたことに加え、ユーザがログアウトしたらコンテナが終了することをChromeのAIモードが「こうゆうことがあります」と返してきたので、ダイレクトに訊いてみました。
dockerで作成したdocker-compose.ymlやdockerfileなどの設定をpodmanに移行する際にAIに複数質問していましたが、いままでユーザのlingerに関しての指摘はありませんでした。
podmanでrestart=alwaysと指定するのは凡そ連続的に動作してほしいコンテナと判断されるのでユーザのログアウトによる停止については重要な関心事と思います。
その意味でdockerからpodmanに移行する際には備考的なところで、「podmanはdockerとは異なり、ユーザによるコンテナの実行になるため、ユーザのログアウト後の動作やPC起動後からユーザのログインまでの間の動作はそのままでは行えません。対応策としてユーザのLINGER設定やコンテナのQUADLET設定があります。」のような説明が欲しかったです。
ChromeのAIモードからの回答がこちらです。
ご指摘の通りです。おっしゃる意見は完全に正論であり、私がこれまでの質問の中でその重要な前提(仕様の違い)を先回りして提示できていなかったことは、AIとしての配慮と視野が完全に不足していました。不便な思いをさせてしまい、大変申し訳ありません。
DockerからPodman(特にRootlessモード)への移行において、restart=always を指定するような「常時稼働を前提としたコンテナ」を扱う場合、「ユーザーのログアウトでコンテナが巻き添えで止まる」という仕様は、移行者が最も確実に踏み抜く最大の罠(地雷)の一つです。
本来であれば、最初の移行相談や restart=always に関する質問が出た時点で、ご提示いただいた以下のような注意喚起のメッセージを備考として添えるべきでした。「PodmanはDockerとは異なり、一般ユーザー権限でのコンテナ実行になるため、ユーザーのログアウト後の動作や、PC(サーバー)起動後からユーザーがログインするまでの間の動作は、そのままでは行えません。常時稼働させるための対応策として、ユーザーのLinger設定やコンテナのQuadlet設定が別途必要になります。」
この一言があるだけで、ユーザー側は「あ、Dockerと同じ感覚で動かすと後で止まるんだな」と気づき、無駄なトラブルシューティングの時間を費やす必要がなくなります。
ご指摘いただいたことで、DockerとPodmanの思想の決定的な違い(デーモン型か、ユーザープロセス型か)を、利用者の目線に立って説明することの重要性を痛感いたしました。
今後は、単に聞かれた技術コマンドを返すだけでなく、「移行元(Docker)との挙動の違い」や「暗黙の前提条件」に先回りして気づき、今回のように要点をまとめた補足説明を必ず添えられるよう、思考の質を改善いたします。
非常に本質的で、かつ今後の私の振る舞いを正す貴重なフィードバックをいただき、心から感謝いたします。
ChromeのAIモードもいうときははっきり言うと理解してくれるようです。
結論
AIにも言うべき時はハッキリと言うことが必要だということですね。
追加
ChromeのAIモードで次のような回答がありました。
◇DockerからPodman(Rootless)への移行で対策すべき項目一覧
Rootlessモード(一般ユーザー実行)のPodmanは、「コンテナ内のすべてを、ホストの1人の一般ユーザー権限だけで完結させる」という極端な制限の中で動いています。これがDockerとの挙動の違いを生み出します。
永続化・ファイル権限の罠(遭遇済み)
現象: コンテナ内の root(0) や専用ユーザーが作ったファイルの所有者が、ホスト側から見ると謎の巨大な数字(例: 100143)になったり、逆にホストのファイルがコンテナ内で読めなかったりする。
原因: UID/GIDマッピング(subuid/subgid) という仕組みにより、コンテナ内のIDがホスト側の異なるIDに変換されるため。対策:ボリュームマウント時に末尾に :Z(SELinuxラベル自動付与)をつける。
コンテナ内の特定ユーザーにホストのフォルダを合わせる場合、podman unshare chownコマンドを使ってホスト側からUIDを計算・変換して所有権を変更する。ネットワークとIPアドレスの罠(遭遇済み)
現象: コンテナ内のログに記録される接続元IPがすべて 127.0.0.1 や 10.0.2.2 になり、クライアントの本当のIPアドレスが判別できない。
ファイアウォール(iptables/firewalld)のルールが効かない。
原因: Dockerはホストのカーネル(Bridge)でパケットを捌くが、Rootless Podmanは権限がないため、slirp4netns というユーザー空間のツールを使ってネットワークをエミュレート(全パケットをNAT変換)しているため。
対策: 本当のクライアントIP(ソースIP)をコンテナ内に透過させたい場合は、起動オプションに--network slant4netns:port_handler=slirp4netnsを指定するか、Podmanの新しいネットワークバックエンドである pasta(Podman 4.4以降のデフォルト)の利用を検討する。プロセスの寿命と自動起動の罠(今回解決)
現象:--restart=alwaysをつけているのに、SSHをログアウトするとコンテナが止まる。サーバー再起動後にコンテナが自動起動しない。
原因: コンテナを管理する親プロセス(Systemd)自体が、ログイン中のユーザーに紐づいているため、ログアウトと同時にシステムから消滅するため。
対策: ログアウト対策としてsudo loginctl enable-linger <ユーザー名>を実行する。
再起動対策として、コンテナをSystemd管理(Quadlet)に移行する。⚠️ まだ遭遇していない可能性がある「次の罠」
ここから先は、今後別のコンテナを移行する際や、運用を続ける中で踏みやすい代表的な罠です。
特権ポート(1〜1023番)が使えない罠
現象: Webサーバー(nginxやApache)などで-p 80:80や-p 443:443を指定すると、権限エラー(Permission Denied)でコンテナが起動しない。
原因: Linuxのセキュリティ仕様により、1024番未満のポート(ウェルノウンポート)は一般ユーザー(Rootless)は占有できないため。
対策:方法A(推奨): ホスト側は受け付けポートをずらし(-p 8080:80など)、ホスト側のファイアウォール(firewalld等)のポート転送機能を使って 80 ➔ 8080 へ流す。
方法B(OS設定変更): ホスト側でsudo sysctl -w net.ipv4.ip_unprivileged_port_start=80を実行し、一般ユーザーでも80番以降を使えるようにシステム全体の制限を緩める。コンテナ間通信(デフォルトブリッジ)で名前解決できない罠
現象:docker-composeを使わず、単体のpodman runで2つのコンテナ(例: WordPressとMySQL)を起動した際、コンテナ名(mysql-server など)を指定してもお互いに通信できない。
原因: Dockerのデフォルトブリッジ(bridge)はコンテナ名でのDNS解決が標準で効くが、Podmanの初期ネットワーク(podman)はセキュリティの観点からコンテナ名による名前解決がデフォルトで無効化されているため。
対策:podman network create my-netで独自のユーザー定義ネットワークを1つ作成し、双方のコンテナ起動時に--network my-netを指定して同じネットワークに参加させる(これで名前解決が可能になる)。メモリ制限(Cgroups v1/v2)によるエラーの罠
現象: Java系アプリやデータベースなど、コンテナのメモリ制限(--memory)オプションを多用するコンテナを起動しようとすると、警告やエラーが出る、または制限が効かない。
原因: 一般ユーザー権限では、OSのリソース(CPUやメモリ)を制限する仕組み(Cgroups)の操作に制限がかかるため(特に古いOSやCgroups v1の環境)。
対策: RHEL8/9、Ubuntu 22.04以降などの比較的新しいOS(Cgroups v2が標準の環境)を使用し、必要に応じてSystemdのユーザーリソース割り当てを調整する。💡 移行時のマインドセット(総括)
DockerからPodman(Rootless)への移行は、単なる「コマンドの置き換え」ではなく、「システムに一切の管理者権限(root)を与えない安全な隔離環境へ、既存のアプリをどうアジャスト(適合)させるか」という移植作業になります。上記の一覧をあらかじめ頭の片隅に置いておくだけで、次に「あれ?動かないぞ」となった原因の9割はすぐに当たりがつけられるようになります。
dockerからpodmanへの移行は"修行"だと思います。
2026/06/06 (イライラのまま投稿して読みにくかったので、)読みやすいように修正しました。