実践ガイド

bind mount と UID/GID 問題

ホストとコンテナの UID/GID がずれて「コンテナが作ったファイルがホストから消せない」「Permission denied」に陥る問題を、原因と対処のパターンで整理します。

bind mount と UID/GID 問題 diagram

なぜズレるのか

Linux ではファイルの所有者は UID(数値) で管理されており、名前はあくまで /etc/passwd 経由の飾りです。

ホストの alice(UID 1000)が作ったファイルも、コンテナ内からは「UID 1000 のファイル」としか見えません。

コンテナ内で root(UID 0)として動くプロセスがホストの bind mount 上にファイルを作ると、ホスト側にも UID 0(= root)所有のファイルができます

これにより、

  • ホスト側の一般ユーザーでは削除・書き換えができない
  • .next / dist / node_modules のようなビルド成果物が毎回 root 所有になる

という現象が起きます。

rootless Podman や user namespace を使うと緩和されますが、素の Docker + Linux 環境で最もよく遭遇する問題です。

症状あるある

  • rm -rf node_modulesPermission denied になる → コンテナ内 root が作ったため
  • VS Code で「ファイル保存できません」 → マウント先が root 所有になり、ホストの自分に書き込み権限がない
  • git status で変なモードビット変更が出る → Windows / WSL2 でファイルシステムを跨ぐと、実行ビットがばらつくことがある
  • CI で COPY . . 後にパーミッションが壊れる → ビルドコンテキスト内のファイルの所有者が想定外

macOS / Windows の Docker Desktop では内部で UID マッピングが働くので気づきにくいですが、Linux ホストや WSL2 内で bind mount したときに顕在化します。

対処 1: 非 root ユーザーで動かす + UID を合わせる

Dockerfile で非 root ユーザーを作成し、実行時にホストユーザーの UID/GID に合わせる方法です。

FROM node:20-alpine

ARG UID=1000
ARG GID=1000
RUN addgroup -g ${GID} app && adduser -D -u ${UID} -G app app

WORKDIR /app
USER app
COPY --chown=app:app . .
CMD ["node", "server.js"]
docker build --build-arg UID=$(id -u) --build-arg GID=$(id -g) -t my-app .
docker run --rm -v "$PWD:/app" my-app

ホストユーザーと同じ UID/GID でプロセスを動かすので、bind mount 越しに作られるファイルもホストから自然に扱えます。

対処 2: --user フラグで上書きする

Dockerfile をいじれない場合(公式イメージを使っている等)は、docker run --user で実行時に UID/GID を指定できます。

docker run --rm -u "$(id -u):$(id -g)" -v "$PWD:/app" -w /app node:20 node server.js

注意点:

  • 指定した UID がイメージ内の /etc/passwd に無くても動作するが、一部アプリは whoami で「UID xxxx」と表示されて混乱する
  • UID が HOME を持たない場合、npm など一部ツールがエラーになる。-e HOME=/tmp を足すか、書き込み可能な HOME を用意する
  • ポート 80/443 など 1024 未満を bind する用途には使えない(非 root 権限では通常不可)

対処 3: 名前付きボリュームで分離する

ホストと UID を合わせる必要がないデータ(node_modules、キャッシュ、ビルド成果物)は、bind mount から外して 名前付きボリュームに分けるのが手堅いです。

services:
  web:
    build: .
    volumes:
      - ./:/app              # ソースは bind mount(編集が即反映)
      - node_modules:/app/node_modules  # 名前付きボリュームで分離
volumes:
  node_modules:

こうすれば、node_modules はコンテナ内の UID で書かれてもホストからは見えないので権限問題が起きません。

ソース側は UID 合わせの対策と併用します。

rootless / user namespace という根本解決

Podman の rootless モードや Docker の userns-remap を有効にすると、コンテナ内の UID 0 がホストの UID 0 にマッピングされないため、この問題が構造的に解消されます。

  • Podman rootless: 追加設定なしで既定動作。コンテナ内の root はホストの自分に、その他の UID は subuid / subgid の範囲にマップされる
  • Docker userns-remap: /etc/docker/daemon.json"userns-remap": "default" を書いてデーモン再起動

ただし、この設定下では 既存の bind mount の所有権調整が必要になったり、特権を要するコンテナ(Docker-in-Docker 等)が動かなくなるケースがあります。

導入前に CI と開発フローで一通り動作確認をしてください。

関連トピック