Docker ComposeのPUIDとPGIDの役割と設定方法
PUIDとPGIDはDockerの設定ではなく、linuxserver.ioイメージのentrypoint規約です。bind mountが911:911になる理由と、正しいUID・GIDで直す方法を解説します。
PUID と PGID の実際の役割
PUID と PGID は、一部のコンテナイメージが起動時に読み取る環境変数です。Docker 自体はこれらを参照しません。これは linuxserver.io のイメージや一部の他のイメージで使われる慣例です。そのため、これらを読み取るように作られていないイメージでは、設定しても無視されます。
linuxserver.io のイメージ内には、abc というユーザーがあります。このユーザーはビルド時に UID (user ID) 911、GID (group ID) 911 で作成されます。コンテナは root として起動し、初期化スクリプトを実行します。そのスクリプトの 1 つが、他の処理を始める前にこのユーザーの ID を変更します。
groupmod -o -g "${PGID}" abc
usermod -o -u "${PUID}" abc-o フラグを指定すると、別の場所ですでに使用されている ID も指定できます。その後、初期化処理が権限を落とし、アプリケーションを abc として実行します。したがって、PUID=1000 が Docker に渡されることはありません。この変数は、アプリケーションの起動前にコンテナ内のユーザーの ID を変更します。その結果、アプリケーションが書き込むすべてのファイルは、ホスト上で 1000 所有になります。PUID を設定しない場合、abc は 911 のままです。そのため、設定していない bind mount には 911:911 が所有するファイルが増えていきます。
id で 2 つの番号を取得します
データディレクトリの所有者であるユーザーとして、ホスト上で次のコマンドを実行します。
iduid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo),988(docker)uid が PUID、gid が PGID です。スクリプトでは、id -u と id -g により番号だけを出力できます。新規作成した VPS イメージの多くでは、最初の一般ユーザーが 1000:1000 ですが、これを前提にしないでください。サーバーを再構築した場合や、後から 2 つ目のアカウントを追加した場合は、1001 以上になることがあります。ここで番号を間違えると、それが問題のすべての原因になります。サービスを 自分のログインユーザーではなく専用のサービスアカウントで実行する場合は、id thatuser を実行し、その結果から番号を取得します。
ファイルが 911:911 と表示される理由
ls -l は、その ID に一致するホスト側のアカウントがない場合、名前ではなく数値 ID を表示します。サーバー上に UID 911 は存在しないため、表示する名前がありません。毎回数値で確認して曖昧さをなくすには、ls -ln を使用します。
ls -ln /srv/appdata/sonarrdrwxr-xr-x 2 911 911 4096 Aug 7 09:12 Backups
-rw-r--r-- 1 911 911 512 Aug 7 09:12 config.xmlこの出力は、コンテナが組み込みのデフォルト値で実行されたことを示しています。推測せず、コンテナ内から確認してください。
docker exec sonarr id abc
docker compose logs sonarr | head -n 25linuxserver の init は、起動ログに結果を次の 2 行で出力します。
User UID: 911
User GID: 911Compose ファイルで PUID=1000 を設定した後もこれらの行が 911 になる場合、変数はコンテナに渡っていません。通常の原因は、docker-compose.yml を編集した後に docker compose restart を実行したことです。このコマンドは、元の環境変数を保持したまま既存のコンテナを再利用します。環境変数の変更には docker compose up -d が必要です。このコマンドでコンテナを再作成します。
コンテナが書き込んだファイルを削除できない理由
カーネルが比較するのは名前ではなく番号です。シェルは UID 1000 で実行されています。ファイルの所有者は UID 911 です。ファイルを格納しているディレクトリは drwxr-xr-x で、これも 911 の所有です。そのため、グループとその他のユーザーには読み取りと実行が許可されていますが、書き込みは許可されていません。ファイルを削除するには、ファイル自体ではなく、そのディレクトリへの書き込み権限が必要です。そのため、ファイル自体に問題がないように見えても、次のエラーが発生します。
rm: cannot remove '/srv/appdata/sonarr/config.xml': Permission denied書き込むコンテナも、反対側から同じ制限を受けます。ホストのディレクトリが mode 755 でユーザー所有になっていて、アプリケーションが 911 として実行される場合、最初の書き込みは Permission denied で失敗します。アプリケーションは、そのエラーを独自のメッセージで報告します。Sonarr や Radarr などの .NET アプリケーションでは、UnauthorizedAccessException: Access to the path '/data/downloads' is denied として表示されます。ファイル名の前に表示される権限文字列から、3 つの権限セットのうち、どれに基づいて判定されているかが分かります。drwxr-xr-x を正しく読み取ることで、このエラーを不可解なものではなく、明確なものとして理解できます。
これは特に bind mount で発生する問題です。Docker が空の named volumeを作成し、イメージ内に存在するパスへマウントすると、そのパスの内容を所有者情報と権限ビットを含めて volume にコピーします。そのため、アプリケーションは自分がすでに所有しているディレクトリを使用できます。bind mount にはこの処理がありません。Docker はホストのディレクトリを、そのままマウントします。この違いがあるため、bind mount が named volume より適する場合と、適さない場合を理解しておくことが実務上重要です。
すでに不整合があるディレクトリを修正する
PUID と PGID の設定は、以後のアプリケーションの動作を変更します。ディスク上にすでに存在するファイルは、遡って修正されません。スタックを停止し、自分で所有者を修正してから、再度起動します。
docker compose down
sudo chown -R 1000:1000 /srv/appdata/sonarr
docker compose up -d数値を入力したくない場合は sudo chown -R "$(id -u):$(id -g)" /srv/appdata/sonarr を使用します。コンテナを停止した状態で実行してください。アプリケーションが書き込み中に再帰的な chown を実行すると、ディレクトリツリーの修正が不完全になり、原因の分かりにくいエラーが再び発生する可能性があります。
PUID と PGID で解決できないこと
ここは、手順を正しく実行した場合でも問題になる部分です。linuxserver の init は起動時に、/app、/config、/defaults の3つのパスだけに対して chown を実行します。メディアのマウントはこの一覧に含まれません。/data、/downloads、/tv は変更されずにアプリケーションへ渡されます。そのため、これらのマウントのホスト側で、コンテナユーザーが書き込めない所有者が設定されていると、コンテナは正常に起動し、バナーには正しい UID が表示されますが、最初のインポートで失敗します。
これは正しい動作です。コンテナを起動するたびに、12 TB のメディアライブラリ全体に対して再帰的な chown を実行すると、重大な問題になります。そのため、メディアディレクトリの管理は利用者の作業です。また、実際に権限の問題が発生するのもこれらのマウントです。
ユーザーを制御する3つの方法と、それぞれを使用する場面
PUID と PGID の環境変数
これは、entrypoint がこれらの環境変数を読み取るイメージでのみ機能します。コンテナは root として起動し、独自の初期設定を行い、/config を修正してから権限を下げるため、広く使われています。Docker Mods とカスタム init スクリプトも引き続き動作します。一方で、プラットフォームの機能ではなく慣例に依存する点がコストです。また、変数名はプロジェクト間で標準化されていません。
Compose の user: キー
これは実際の Docker 機能です。イメージ独自のコードが実行される前にコンテナランタイムが適用するため、すべてのイメージで機能します。
services:
sonarr:
image: lscr.io/linuxserver/sonarr:latest
user: "1000:1000"プロセスは一瞬たりとも root として実行されません。これは実質的なセキュリティ向上です。ただし、root 権限を必要とする entrypoint の処理は動かなくなります。linuxserver のイメージでは、プロジェクトがこれを合理的な範囲でサポートしています。ただし、対象はプロジェクトがテストしたイメージに限られます。具体的な注意点は、PUID と PGID が効果を持たなくなること、Docker Mods が実行されないこと、カスタムサービスが実行されないこと、そしてマウントするすべての volume の権限を自分で管理する必要があることです。公式ドキュメントのパターンでは、このフラグと書き込み可能な /run を組み合わせます。
user: 1000:1000
tmpfs:
- /run:uid=1000,gid=1000,exec
security_opt:
- no-new-privileges=true見た目上の副作用に驚く人もいます。数値の user: に対応するエントリがコンテナ内の /etc/passwd にないため、内部のツールは whoami: cannot find name for user ID 1000 と表示します。ID は有効で、ファイルアクセスも通常どおり機能します。失敗するのは名前の解決だけです。
Rootless Docker
Rootless Docker では、デーモン自体を権限のないユーザーとして実行します。そのため、ホスト上では実際の root として実行されるものがありません。所有権の計算方法も完全に変わります。コンテナの UID 0 は、rootless Docker を実行しているホストユーザーの UID にマッピングされます。また、n が 1 以上の場合、コンテナの UID n は subuid + (n - 1) にマッピングされます。subuid は /etc/subuid に割り当てられた範囲の基準値で、/etc/subgid です。Docker では、そこに少なくとも 65,536 個の subordinate ID が必要です。
このマッピングは、通常のアドバイスを逆転させるため、もう一度確認してください。Rootless Docker では、root として書き込むコンテナが作成したファイルは、ホストユーザーであるあなたの所有になります。一方、UID 1000 として書き込むコンテナが作成したファイルは、100999 前後の subordinate ID が所有することになり、シェルから操作できません。そのため、rootful daemon で正しい PUID 値が、ここでは誤った値になります。2つの仕組みは異なる層で同じ問題を解決します。確認せずに重ねて使うと、sudo が必要なディレクトリを削除できなくなります。Rootless に移行する場合は、ライブラリを移行する前に、自分のサーバーで書き込まれたファイル1つの所有権を確認してください。
単一の VPS 上で運用する多くの self-hosted 構成では、rootful daemon で PUID と PGID を指定する方法が現実的です。これは、イメージがその方法を前提にビルドされ、ドキュメント化されているためです。イメージの README に user: 用にテスト済みと記載されている場合、または PUID をまったくサポートしない公式の upstream イメージを使う場合に、user: を選択します。
1 台の VPS 上で self-hosted AFFiNE インスタンスを運用する構成は後者に該当します。どのコンテナも PUID を読み取らず、データベースディレクトリとアップロードファイルの所有者は、環境ブロックの設定ではなく runtime によって決まるためです。同じことがself-hosted Chatwoot のサポートデスクにも当てはまります。Rails コンテナと Sidekiq worker は同じ uploads ディレクトリに書き込みますが、どちらも PUID を読み取りません。そのため、このディレクトリはイメージが実際に実行するユーザーに合わせる必要があります。
新しい stack でもこの点は変わりません。したがって、チームの各メンバーに独自の sandbox 化された OneCLI agent を割り当てる構成では、メンバーごとの workspace ディレクトリと Postgres のデータディレクトリは、各イメージが実行するユーザーの所有になります。これは PUID の問題ではなく、user: と chown の問題です。
Codex、Claude Code、Hermes の前段に単一の self-hosted API を置く構成でも、同じ構成を引き継ぎます。このイメージは組み込みの独自ユーザーで実行され、データベースと保存済みの key を保持する bind mount の所有者は、そのユーザーの所有者情報になります。
メディアスタックの例: コンテナ間で共有するグループ
Sonarr、Radarr、ダウンロードクライアントを含む arr メディアスタックでは、これが理論ではなく実際の問題になります。ダウンロードクライアントは、完了したファイルを /data/downloads に書き込みます。Sonarr はそのファイルを /data/media にハードリンクするか、移動します。ハードリンクを機能させるには、2 つのコンテナが同じツリーへの書き込み権限を持つ必要があります。ダウンロードクライアントが 1000 で実行され、Sonarr が 1001 で実行されている場合、一方が所有するファイルをもう一方は読み取りしかできません。
この問題は、スタック内のすべてのコンテナが PGID として使用する共有グループを作成して解決します。
sudo groupadd -g 13000 media
sudo usermod -aG media deploy
sudo chown -R deploy:media /srv/media
sudo find /srv/media -type d -exec chmod 2775 {} +
sudo find /srv/media -type f -exec chmod 0664 {} +2775 の先頭にある 2 は setgid ビットです。ディレクトリでは、その中に作成されるすべての新しいファイルとサブディレクトリが、作成者自身のプライマリグループではなく media グループを継承することを意味します。そのため、新しいダウンロードのたびに chown を再実行する必要がありません。自身のアクセス権を確認する前に、ログアウトしてから再度ログインするか、newgrp media を実行してください。usermod -aG で追加したグループは、すでに開いているシェルセッションには反映されません。
コンテナ内では、groupmod -o -g 13000 abc によって abc グループの番号を 13000 に変更します。これにより、abc はホストの media グループと同じ GID で書き込みます。スタック内の各コンテナはそれぞれ独自の PUID を保持し、1 つの PGID を共有します。
次に、スタック内のすべての linuxserver コンテナで UMASK=002 を設定します。ここを見落とす人が多くいます。これらのイメージのデフォルトは UMASK=022 です。これは新しいファイルからグループの書き込みビットを取り除くため、ファイルは 0644 として作成され、設定した共有が機能しません。002 は 0664 ファイルと 0775 ディレクトリを作成し、グループによる書き込みを可能にします。
services:
sonarr:
image: lscr.io/linuxserver/sonarr:latest
container_name: sonarr
environment:
- PUID=${PUID}
- PGID=${PGID}
- UMASK=002
- TZ=Etc/UTC
volumes:
- /srv/appdata/sonarr:/config
- /srv/media:/data
restart: unless-stoppedこの 2 つの値は Compose ファイルの隣にある .env ファイルに記述します。これにより、スタック全体で同じ定義を読み取れます。
PUID=1000
PGID=13000Compose はそのファイルを自動的に読み込み、${PUID} 形式の置換に使用します。これは認証情報にも使用する仕組みです。値を docker-compose.yml に直接記述せず .env ファイルに分離する運用もここで適用できます。ただし、この 2 つの数値は Secret ではありません。
設定をそのまま信頼せず、エンドツーエンドで検証します。一方のコンテナ内からファイルを書き込み、ホストから読み取ります。
docker exec sonarr touch /data/downloads/permtest
ls -ln /srv/media/downloads/permtest正常な結果では、所有者に PUID、グループに 13000、モードに -rw-rw-r-- が表示されます。グループが 1000 と表示される場合、そのディレクトリに setgid ビットがありません。モードが -rw-r--r-- と表示される場合、UMASK 変数が反映されていません。コンテナを再起動しただけでなく、再作成したことを確認してください。完了したら、rm /srv/media/downloads/permtest でテストファイルを削除します。
使用するイメージと変数の対応
linuxserver.io のイメージでは PUID、PGID、UMASK を使用します。Paperless-ngx では同じ考え方に異なる名前を使います。USERMAP_UID と USERMAP_GID で、どちらもデフォルト値は 1000 です。ドキュメントでは、これらの値を id -u と id -g から読み取るよう案内されています。写真サーバーでも違いがあります。PhotoPrism には独自の PHOTOPRISM_UID と PHOTOPRISM_GID の組み合わせがあります。一方、Immich には同等の仕組みがなく、コンテナのユーザーを Docker の user: キーに任せます。そのため、PhotoPrism と Immich のどちらを選ぶかによって、サーバー上の最大のライブラリに対して、どの仕組みを保守することになるかも決まります。一般的なデータベースや Web サーバーのイメージを含む、多くの公式 upstream イメージでは、固定された組み込みユーザーを使用し、user: を使うか、変更せずにそのまま使用することを想定しています。後から追加するインフラにも同じことが当てはまります。そのため、アプリの前段に Authentik を置いてシングルログインにする構成では、公式の server、Postgres、Redis イメージを実行することになります。これらは PUID をまったく読み取らず、ボリュームの所有権は設定可能な entrypoint ではなく、runtime によって決まります。
そのため、プロジェクト間で環境変数のブロックをコピーする前に、各イメージの README を確認してください。Docker は、設定した環境変数を、その変数を内部で読み取るかどうかに関係なく、すべてのコンテナへ渡します。何も読み取らない PUID を設定しても、エラー、警告、効果は発生しません。コンテナは、その Dockerfile の末尾で指定されたユーザーとして実行されます。実際にどのユーザーで実行されたかは、コンテナが書き込んだファイルの所有権から確認できます。
FAQ
Docker ファイルの所有者が 911:911 になるのはなぜですか?
911 は、linuxserver.io イメージに組み込まれた abc ユーザーの UID と GID です。これが表示される場合、コンテナの起動時に PUID と PGID が設定されていないため、init スクリプトが組み込みのデフォルト値をそのまま使用しています。ホストに ID 911 のアカウントがないため、ls -l には名前ではなく数値が表示されます。id の出力を PUID と PGID に設定し、docker compose up -d でコンテナを再作成してから、対象ディレクトリに対して sudo chown -R 1000:1000 を実行し、既存ファイルの所有者を修正します。
PUID と PGID はすべての Docker イメージで機能しますか?
いいえ。これらは Docker の機能ではなく、Docker が読み取ることもありません。イメージ自身のエントリポイントがこれらを読み取り、アプリケーションの起動前に usermod と groupmod を呼び出すイメージでのみ機能します。該当するのは linuxserver.io のイメージ群と、この方式を採用した一部のプロジェクトです。プロジェクトによっては、paperless-ngx の USERMAP_UID と USERMAP_GID のように別の名前を使用します。どちらの変数も読み取らないイメージでは、変数は警告なしに受け付けられ、無視されます。
Docker Compose では PUID と PGID、それとも user: キーを使用すべきですか?
イメージが対応している場合は PUID と PGID を使用してください。エントリポイントが root として一定時間実行されるため、/config を修正し、独自のサービスを正しく起動できます。イメージが PUID に対応していない場合、またはイメージの README に非 root での動作がテスト済みと記載されている場合は、user: を使用します。linuxserver のイメージで user: を設定すると、PUID と PGID は機能しなくなり、Docker Mods とカスタムサービスが実行されなくなります。また、マウントしたすべてのボリュームの権限を自分で管理する必要があります。
Sonarr の PUID は正しいのに、ファイルを移動できません。何が問題ですか?
次の3点を順番に確認してください。まず、メディアのマウント自体を確認します。init が chown するのは /app、/config、/defaults だけなので、/data または /downloads はホスト上の所有権を維持します。次に、共有グループを確認します。ダウンロードクライアントと Sonarr が異なる GID で実行されていると、互いのファイルを変更できません。そのため、スタック内のすべてのコンテナに同じ PGID を指定します。最後に、umask を確認します。イメージのデフォルト値 UMASK=022 では、ファイルがグループ書き込み権限なしの 0644 で作成されるため、共有グループが機能しません。UMASK=002 を設定し、chmod 2775 でディレクトリに setgid ビットを設定すると、新しいファイルがグループを継承します。