動かない時

bind mount で permission denied

bind mount したディレクトリにコンテナから書き込めない、読めない問題。UID/GID 不一致、SELinux の `:z`/`:Z` ラベル、rootless コンテナの user namespace を順に切り分けます。

bind mount で permission denied diagram

症状

  • docker run -v $(pwd):/app ... でコンテナ内から /app に書き込むと Permission denied
  • 書けはするがホスト側から見ると root:root の所有になってしまい後で困る
  • RHEL / Fedora / CentOS Stream で 何もしていないのに Permission denied(他のディストリでは動くのに)
  • rootless Podman / rootless Docker で実行中、ホスト UID と違う UID で所有されてしまう

原因 1: UID/GID 不一致

bind mount は Linux カーネルにとって「ホストの同じディレクトリ」そのものです。

ファイルの所有 UID/GID はホストの値であり、コンテナ内のプロセスが書き込むときも UID はコンテナプロセスのものになります。

  • ホスト側でディレクトリが UID=1000 所有で mode=755rwxr-xr-x)になっている
  • コンテナ内アプリは USER node(UID=1000 のことが多い)や root(UID=0)で動いている
  • UID が一致せず、かつ others に書き込み権限がないため失敗する、または root で書いた結果ホストから見て root:root になる

原因 2: SELinux ラベル(RHEL 系)

RHEL・Fedora・CentOS Stream など SELinux が enforcing で動く環境では、bind mount 元のディレクトリがコンテナ用のラベル(container_file_t)を持っていないとコンテナから触れません。

UID が正しくてもカーネル層で拒否されるため、ls -l からは原因が分からず詰まります。

ls -lZ で SELinux コンテキストを見ると unconfined_u:object_r:user_home_t:s0 のようにホーム用のラベルのままになっています。

これが container_file_t に変わっていないのが原因です。

原因 3: rootless の user namespace(UID シフト)

rootless Podman や rootless Docker では、コンテナ内の UID=0 はホストから見ると「UID=100000+α」といった別の UID にマッピングされます(/etc/subuid の設定による)。

  • ホストで自分の UID(例: 1000)で所有しているディレクトリを bind mount する
  • コンテナ内で UID=0(ホストから見ると 100000)として書き込むと Permission denied
  • コンテナ内で意図的に UID=1 で動かす設定にすると、ホストから見ると UID=100001 で所有され、元ユーザーから触れないファイルが生成される

確認コマンド

bash
# ホスト側: 所有とパーミッション
ls -la ./data
stat ./data

# ホスト側: SELinux コンテキスト(RHEL 系)
ls -laZ ./data
getenforce   # Enforcing / Permissive / Disabled

# コンテナ内: 実効 UID/GID
docker run --rm -v $(pwd)/data:/app alpine id
docker run --rm -v $(pwd)/data:/app alpine stat /app

# rootless 環境の UID マッピング
cat /etc/subuid /etc/subgid
podman unshare cat /proc/self/uid_map

解決策

  • UID/GID 不一致:
    • コンテナ側の UID をホストに合わせる: docker run --user $(id -u):$(id -g) -v $(pwd):/app ...
    • Dockerfile で USER 1000 などと固定し、ホスト側で同じ UID を使う運用に揃える
    • 書き込み対象は専用のデータディレクトリに切り、ホスト側で chown -R 1000:1000 ./data
  • SELinux(RHEL 系):
    • 共有マウント(他コンテナとも共有して良い)なら :z を付ける: -v $(pwd):/app:z
    • 専用マウント(このコンテナ専用)なら :Z を付ける: -v $(pwd):/app:Z
    • どちらもカーネルが自動で container_file_t ラベルを設定する。:Z は他コンテナから触れなくなるので、共有の有無で使い分ける
    • 切り分け中にだけ setenforce 0 で一時的に Permissive にするのは OK。恒久的に無効化する運用は避ける
  • rootless:
    • ホスト UID をそのままコンテナに持ち込む: podman run --userns=keep-id ...(Podman)または Docker の --userns-remap 設定を見直す
    • 書き込み先はホームの書ける場所($HOME 配下)にし、/etc/subuid のレンジがログインユーザーに割り当てられていることを確認
  • 対症療法としての緩和策:
    • どうしても急ぐときはホスト側で chmod 777 ./data が動くが、本番・共有環境では厳禁。切り分けが終わったら必ず戻す