「named volumeの中身をホストから見たら、所有者がrootや見覚えのない数字になっていて編集できない」
Dockerを実務で使い始めると、ほぼ全員が一度はこのUID・GIDのズレにつまずきます。
この記事では、ホストとコンテナでファイルの所有者がズレる仕組みから、DockerfileのARG・USER命令、docker-compose.ymlのuser指定、entrypointでの動的調整まで、実機の出力例つきで解決策を解説します。
動作確認環境: Rocky Linux 9.4 / Ubuntu 24.04 LTS(Docker Engine 26.x・Docker Compose Plugin v2.27)
この記事のポイント
・Permission deniedの正体は、ホストとコンテナでUID・GIDの数値が一致しないこと
・DockerfileのARG+USER命令で、ビルド時にホストのUIDへ合わせられる
・docker-compose.ymlのuser: "1000:1000"で実行時にも指定できる
・配布イメージにはPUID/PGID環境変数+entrypointでの動的chown設計が定番
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜホストとコンテナでファイル権限がズレるのか|UID・GIDの正体
Linuxのファイル権限は、ユーザー名ではなくUID(ユーザーID)とGID(グループID)という数値で管理されています。ls -lで表示される「ubuntu」「www-data」といったユーザー名は、/etc/passwdにある名前とUIDの対応表を見て表示しているだけで、ファイル自体にはUIDの数値しか記録されていません。Dockerのコンテナは、Linuxカーネルのnamespace機能でプロセスやファイルシステムの「見え方」を隔離していますが、UID・GIDの数値そのものは隔離していません(userns-remap未使用の場合)。
つまり、コンテナ内で「UID 1000のappuser」が作成したファイルは、ホスト側から見ても単に「UID 1000」のファイルとして扱われます。
ホスト側のUID 1000がたまたま別のユーザー(例えばあなたのログインユーザー)に割り当てられていなければ、ホストから見た所有者は「不明なUID」として表示され、書き込み権限がないと編集・削除ができなくなります。
1. bind mountで症状が出やすい理由
-v ホストのパス:コンテナのパスで指定するbind mountは、ホスト側の実ファイルシステムをそのままコンテナに見せる仕組みです。そのため、コンテナ内プロセスのUIDとホスト側ディレクトリの所有UIDが一致していないと、コンテナ内から書き込もうとした瞬間に
Permission deniedが発生します。$ mkdir -p /data/app && ls -ld /data/app drwxr-xr-x 2 tomohiro tomohiro 4096 8月 22 10:00 /data/app $ docker run --rm -v /data/app:/data node:20-alpine sh -c "id && touch /data/test.txt" uid=1000(node) gid=1000(node) touch: /data/test.txt: Permission denied
tomohiroユーザーのUIDと、コンテナ内nodeユーザーのUIDが異なるため、書き込みが拒否されています。2. named volumeでは症状が遅れて出る
docker volume createで作るnamed volumeは、Docker管理下の領域にファイルを保存する仕組みで、初回作成時はコンテナ内のUID・GIDでディレクトリが初期化されます。このため運用開始直後は問題が出にくく、後から別のUIDで動くコンテナに切り替えたときや、イメージのバージョンアップでコンテナ内ユーザーのUIDが変わったときに、突然Permission deniedが表面化します。
基本的な対処法|ビルド時と実行時にUIDを合わせる3つの方法
3. ホスト側のUID・GIDを確認する
対処の第一歩は、合わせるべき数値を確定させることです。$ id uid=1000(tomohiro) gid=1000(tomohiro) groups=1000(tomohiro),4(adm),27(sudo)
ls -lnで個別に確認しましょう。4. DockerfileのARG・USER命令でビルド時に合わせる
自分でイメージをビルドする場合は、ビルド引数でUID・GIDを受け取り、コンテナ内ユーザーの作成時に反映させます。FROM node:20-alpine ARG APP_UID=1000 ARG APP_GID=1000 RUN addgroup -g ${APP_GID} appgroup \ && adduser -D -u ${APP_UID} -G appgroup appuser USER appuser WORKDIR /app COPY --chown=appuser:appgroup . .
$ docker build --build-arg APP_UID=$(id -u) --build-arg APP_GID=$(id -g) -t myapp . $ docker run --rm -v /data/app:/data myapp id uid=1000(appuser) gid=1000(appgroup)
COPY --chownを併用すると、イメージに焼き込むファイルの所有者もビルド時点でそろえられます。ただし、この方法はイメージがUID固定になるため、複数人・複数サーバーでホスト側UIDがバラバラな環境には向きません。
5. docker-compose.ymlのuser指定で実行時に合わせる
イメージ自体は共通のまま、サーバーごとに実行ユーザーだけ変えたい場合はuser:を使います。services: app: image: myapp:latest user: "1000:1000" volumes: - /data/app:/data
.envと組み合わせてuser: "${APP_UID}:${APP_GID}"のように変数化すれば、サーバーごとに.envを書き換えるだけで済みます。ただし、イメージ側にそのUIDのユーザーが存在しない場合、
idで名前解決できず一部のツールが警告を出すことがある点に注意してください。現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、本記事で紹介したDockerfile・Compose両面でのUID・GID権限設計をさらに深く学べる講座を用意しています。
→ Dockerマスター講座の詳細はこちら >>
応用・実務Tips|PUID/PGID環境変数パターンで配布イメージに対応する
自分で書いたDockerfileならARGやUSERで対応できますが、linuxserver.ioなど配布イメージの多くはビルドせず利用します。その場合によく使われるのが、環境変数
PUID・PGIDをentrypointスクリプトで受け取り、コンテナ起動時に内部ユーザーのUID・GIDを動的に書き換える設計です。6. entrypointでusermod・groupmodを実行する設計
#!/bin/sh # entrypoint.sh PUID=${PUID:-1000} PGID=${PGID:-1000} groupmod -o -g "$PGID" appgroup usermod -o -u "$PUID" appuser chown -R appuser:appgroup /app /data exec su-exec appuser "$@"
services: app: image: myapp:latest environment: - PUID=1000 - PGID=1000 volumes: - /data/app:/data
chown -Rが走るため、コンテナ内に持たせるデータ量が多いイメージでは起動が遅くなることがあります。大容量データを扱う場合は、初回起動時だけchownし、以降はマーカーファイルの有無でスキップする実装にするとよいでしょう。
7. 複数コンテナ・複数サーバーでUIDを統一する設計
同じデータを複数のコンテナ(Webアプリとバックアップジョブなど)が読み書きする構成では、サービスごとにUIDがバラバラだと、片方は書けるがもう片方はPermission deniedという事故が起きます。組織として「アプリコンテナの実行UIDは1000で統一する」というルールを決め、
.envのPUID/PGIDを全サービス共通にしておくと、サーバー移設時のトラブルを大きく減らせます。「Permission denied」が出た時のトラブルシュート
8. bind mountのPermission deniedを切り分ける
$ docker exec myapp id uid=1000(appuser) gid=1000(appgroup) $ ls -ln /data/app drwxr-xr-x 2 1000 1000 4096 8月 22 10:00 .
idとホスト側ls -lnのUID・GIDが一致していれば権限は問題ありません。一致していない場合は、ホスト側で
chown -R 1000:1000 /data/appを実行するか、コンテナ側のUIDをホストに合わせて再ビルド・再起動してください。9. named volumeの所有者ズレを実体パスで確認する
$ docker volume inspect app_data --format '{{ .Mountpoint }}' /var/lib/docker/volumes/app_data/_data $ sudo ls -ln /var/lib/docker/volumes/app_data/_data -rw-r--r-- 1 0 0 1240 7月 10 09:12 config.yml
0(root)のままなのに、コンテナ側は非rootユーザーで動く設定に変えた、というケースが典型的な原因です。docker run --rm -v app_data:/data alpine chown -R 1000:1000 /dataのように、一時コンテナ経由でvolume内の所有者だけを修正できます。10. userns-remapとの組み合わせで二重にズレるケース
コンテナのroot脱出対策としてuserns-remapを有効化している環境では、コンテナ内UIDがさらにホスト側の別範囲(例:100000番台)へ再マッピングされます。このため、PUID/PGID設計だけを見て「合っているはず」と判断すると、実際のホスト側UIDは想定と別の数値になっていることがあります。
迷ったら
docker inspectや実ファイルのls -lnなど、設定ではなく実際のUIDを必ず現物で確認してください。11. よくある原因チェックリスト
・イメージのバージョンアップ:ベースイメージ更新でコンテナ内ユーザーのUIDが変わっていないか・.envの反映漏れ:PUID/PGIDを変更した後、コンテナを再作成(recreate)したか
・chownの範囲不足:サブディレクトリだけ古い所有者のまま残っていないか
・rootlessモード併用:Dockerをrootlessで運用している場合、ホスト側のUID変換ルールも別途確認したか
本記事のまとめ
| やりたいこと | コマンド・設定 |
|---|---|
| ホスト側のUID・GIDを確認する | id |
| ビルド時にコンテナ内ユーザーのUIDを合わせる | DockerfileにARG APP_UIDとUSER命令を追加する |
| 実行時にUIDを指定する | docker-compose.ymlにuser: "1000:1000"を記載する |
| 配布イメージのUIDを起動時に調整する | 環境変数PUID/PGIDとentrypointでのusermod |
| コンテナ内外のUID一致を確認する | docker exec コンテナ名 idとls -ln ホストのパス |
| named volumeの所有者を修正する | docker run --rm -v ボリューム名:/data alpine chown -R UID:GID /data |
自前ビルドならDockerfileのARG・USER命令、配布イメージならPUID/PGID環境変数と、状況に応じて対処法を使い分け、疑わしいときは設定ではなく実ファイルのUIDを現物で確認する習慣をつけておきましょう。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、本記事で紹介したDockerのファイル権限設計をさらに深く学べる講座を用意しています。
→ Dockerマスター講座の詳細はこちら >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:hadolintでDockerfileを静的解析する方法|ベストプラクティス違反の検出とCI組み込みの実践手順
- この記事の属するカテゴリ:Dockerへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます