Dockerのファイル権限エラーを解決する方法|ホストとコンテナのUID・GIDズレをUSER命令とPUID・PGIDで防ぐ設計

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Docker > Dockerのファイル権限エラーを解決する方法|ホストとコンテナのUID・GIDズレをUSER命令とPUID・PGIDで防ぐ設計
「Dockerでbind mountしたディレクトリに、コンテナ側から書き込むとPermission deniedになる」
「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設計が定番


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜホストとコンテナでファイル権限がズレるのか|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)

複数人で運用するサーバーの場合は、bind mount対象ディレクトリの所有者UIDを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など配布イメージの多くはビルドせず利用します。
その場合によく使われるのが、環境変数PUIDPGIDを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_UIDUSER命令を追加する
実行時にUIDを指定する docker-compose.ymlにuser: "1000:1000"を記載する
配布イメージのUIDを起動時に調整する 環境変数PUID/PGIDとentrypointでのusermod
コンテナ内外のUID一致を確認する docker exec コンテナ名 idls -ln ホストのパス
named volumeの所有者を修正する docker run --rm -v ボリューム名:/data alpine chown -R UID:GID /data
UID・GIDのズレは、Dockerを使う誰もが一度はぶつかる地味なトラブルですが、原因は「ホストとコンテナで同じ数値のユーザーになっているか」という一点に尽きます。
自前ビルドならDockerfileのARG・USER命令、配布イメージならPUID/PGID環境変数と、状況に応じて対処法を使い分け、疑わしいときは設定ではなく実ファイルのUIDを現物で確認する習慣をつけておきましょう。

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、本記事で紹介したDockerのファイル権限設計をさらに深く学べる講座を用意しています。
Dockerマスター講座の詳細はこちら >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。