Docker ComposeのPUIDとPGIDとは?911:911の原因と対処法
PUIDとPGIDはDockerの設定ではなく、linuxserver.io imagesのentrypoint慣例です。bind mountのファイルが911:911になる理由と、正しいUID・GIDの設定方法を解説します。
PUID と PGID の実際の意味
PUID と PGID は、一部のコンテナイメージが起動時に読み取る環境変数です。Docker 自体はこれらを参照しません。これは linuxserver.io のイメージや一部の他のイメージで使われる慣例です。そのため、これらを読み取るように作られていないイメージは、設定されていても無視します。
linuxserver.io のイメージ内には、abc というユーザーがあります。このユーザーはビルド時に UID(ユーザー ID)911、GID(グループ ID)911 で作成されます。コンテナは root として起動し、初期化スクリプトを実行します。そのうちの 1 つが、アプリケーションの起動前にこのユーザーの ID を変更します。
groupmod -o -g "${PGID}" abc
usermod -o -u "${PUID}" abc-o フラグを指定すると、別の場所ですでに使用されている ID も設定できます。その後、初期化処理は権限を下げ、abc としてアプリケーションを実行します。つまり、Docker は他の環境変数と同じように PUID=1000 をコンテナへ渡すだけで、その値に基づく処理は行いません。この変数は、アプリケーションの起動前にコンテナ内のユーザー ID を変更します。そのため、user namespace remapping を使用しない標準的な rootful daemon では、作成されたファイルは通常、ディスク上で 1000 の所有になります。既存の書き込み可能なファイルに書き込んでも、所有者は変わりません。Rootless Docker ではこの番号がシフトされます。詳しくは後述します。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 ですが、これを前提にしないでください。割り当ては、存在するアカウントと設定によって決まります。再構築したサーバーでは再び 1000 が割り当てられる場合がありますが、後から作成したアカウントには、利用可能なより大きい ID が割り当てられます。サービスを 自分のログインユーザーではなく専用のサービスアカウント で実行している場合は、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この出力はコンテナに組み込まれたデフォルト値と一致します。ただし、既存ファイルだけでは、現在アプリケーションがどのユーザーで実行されているかは確認できません。この一覧にある config.xmlは認証設定を保持するファイルでもあります。Web UI を初めて開いたときに、Sonarr と Radarr にはデフォルトのユーザー名やパスワードが設定されていないことを確認するうえで重要です。推測せず、コンテナ内から確認します。
docker exec sonarr id abc
docker compose logs sonarr | head -n 25linuxserver の init は、起動ログに結果を 2 行で出力します。
User UID: 911
User GID: 911Compose ファイルで PUID=1000を設定した後も、この 2 行に 911 と表示される場合、実行中のコンテナは設定した値を使用していません。通常の原因は、docker-compose.ymlを編集した後に docker compose restartを実行したことです。この操作では既存のコンテナが再利用され、元の環境がそのまま使われます。環境変数を変更するには docker compose up -dが必要です。これによりコンテナが再作成されます。もう 1 つの原因は意図的な動作です。イメージが読み取り専用モードまたは非 root モードで実行される場合、コンテナ内に変数が存在していても init は ID の変更を完全にスキップします。読み取り専用モードでは、バナーに abcの変更されていない ID が表示されます。非 root モードでは、/runの所有者が表示されます。
コンテナが書き込んだファイルを削除できない理由
kernel は名前ではなく、数値を比較します。shell は UID 1000 で実行されています。ファイルの所有者は UID 911 です。そのファイルを格納するディレクトリは drwxr-xr-x で、これも 911 が所有しています。そのため、group と other には read と execute は許可されていますが、write は許可されていません。ファイルを削除するには、ファイル自体ではなく、そのディレクトリへの write 権限が必要です。そのため、ファイル自体に問題がないように見えても、次の状態になります。
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 として表示されます。ファイルの前に表示される permission string は、3 つの permission set のうち、実際にどれで判定されているかを示します。drwxr-xr-x を正しく読み取ることで、このエラーが不可解なものではなく、明らかなものになります。
これは、特に bind mount に関する問題です。Docker が空の named volumeを作成し、image 内に存在するパスへマウントする場合、そのパスの内容を所有者と permission bit を含めて volume にコピーします。そのため、アプリケーションは自分がすでに所有しているディレクトリを見つけられます。bind mount にはこの処理がありません。Docker はホスト側のディレクトリを、そのままマウントします。この違いが、bind mount が named volume より適する場合と、そうでない場合を理解しておく実際的な理由の 1 つです。
すでに不正な状態になっているディレクトリを修正する
PUID と PGID を設定すると、それ以降のアプリケーションの動作が変わります。ディスク上にすでに存在するすべてのファイルが遡って修正されるわけではありません。ただし、init は以下で説明する特定のパスの所有権を修正します。スタックを停止し、自分で所有権を修正してから、もう一度起動します。
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 のベースイメージにあるユーザー設定スクリプトは、起動時に 3 つのパス(/app、/config、/defaults)の所有者を変更します。メディアのマウント先はこの一覧に含まれません。/data、/downloads、/tv は変更されずにアプリケーションへ渡されます。そのため、これらのマウント先のホスト側で、コンテナユーザーに書き込み権限のない所有者が設定されていると、コンテナは正常に起動し、バナーには正しい UID が表示されますが、最初のインポートで失敗します。
これは正しい動作です。コンテナを起動するたびに、12 テラバイトのメディアライブラリ全体に対して再帰的な chown を実行するのは、重大な問題になります。つまり、メディアディレクトリの管理は利用者の責任であり、実際に権限の問題が発生するのもこれらのマウント先です。この種の障害は、コンテナが正常に見えてから数時間後にアプリケーションログへひっそり現れます。そのため、自分の VPS で ntfy を設定し、スマートフォンへアラートを送信する仕組みに定期的な書き込みテストを組み込むと、エピソードの欠落が 1 週間続く前に問題を把握できます。
ユーザーを制御する3つの方法と、それぞれを使う場面
PUID と PGID の環境変数
これは、entrypoint がこれらの変数を読み取るイメージでのみ機能します。コンテナは引き続き root で起動し、初期設定を行って /config を修正してから権限を下げるため、広く使われています。Docker Mods とカスタム init スクリプトも引き続き動作します。一方で、プラットフォームの機能ではなく慣例に依存する点が欠点です。また、変数名はプロジェクト間で標準化されていません。
Compose の user: キー
これは実際の Docker 機能であり、すべてのイメージで動作します。コンテナランタイムがイメージ内のコードを実行する前に適用するためです。
services:
sonarr:
image: lscr.io/linuxserver/sonarr:latest
user: "1000:1000"プロセスは一瞬たりとも root で実行されないため、セキュリティ上の明確な利点があります。一方で、entrypoint 内で root を必要とする処理は動作しなくなります。linuxserver のイメージでは、プロジェクトがテストしたイメージに限り、合理的な努力の範囲でこの機能をサポートしています。注意点も明確です。PUID と PGID は効果を持たなくなり、Docker Mods は実行されず、カスタムサービスも実行されません。また、マウントするすべてのボリュームの権限を自分で管理する必要があります。ドキュメントに記載された構成では、このフラグを書き込み可能な /run と組み合わせます。
user: 1000:1000
tmpfs:
- /run:uid=1000,gid=1000,exec
security_opt:
- no-new-privileges=true見た目に関する副作用にも注意が必要です。コンテナ内の /etc/passwd に対応するエントリがない数値の user: を使用すると、内部のツールは whoami: cannot find name for user ID 1000 と報告します。ID は有効で、ファイルアクセスも通常どおり機能します。失敗するのは名前の検索だけです。
Rootless Docker
Rootless Docker では、デーモン自体を権限のないユーザーとして実行するため、デーモンとコンテナはホストの root 権限なしで動作します。所有権の計算方法は完全に変わります。コンテナの UID 0 は、Rootless Docker を実行しているホストユーザーの UID にマッピングされます。また、n が 1 以上の場合のコンテナ UID n は、/etc/subuid に割り当てられた範囲の先頭を示す subuid を使って subuid + (n - 1) にマッピングされます。グループ ID も /etc/subgid を使って同じ規則に従います。Docker では、そこに少なくとも 65,536 個の subordinate ID が必要です。
このマッピングは、通常のアドバイスとは逆になるため、もう一度確認してください。Rootless Docker では、コンテナが root としてファイルを作成すると、通常は自分が所有するファイルになります。コンテナが UID 1000 としてファイルを作成すると、通常は subordinate ID が所有するファイルになります。範囲が 100000 から始まる場合は、100999 などになります。シェルからのアクセスは権限と ACL によって決まります。通常、ファイルの削除に必要なのは格納先ディレクトリの権限なので、自分が管理できるディレクトリ内であれば、そのファイルを削除できます。したがって、rootful デーモンで正しい PUID 値が、ここでは誤った値になります。2つの仕組みは異なる層で所有権を変更するため、確認せずに重ねて使うと、マウントしたデータに書き込めなくなることがあります。Rootless に移行する場合は、ライブラリを移行する前に、自分のサーバー上でファイルを1つ作成し、その所有権を確認してください。
単一の VPS で運用するほとんどの self-hosted stack では、rootful デーモン上で PUID と PGID を使う方法が現実的です。イメージがその方法を前提に構築され、ドキュメントにも記載されているためです。イメージの README にテスト済みと記載されている場合、または PUID をまったくサポートしない公式の upstream イメージを使う場合は、user: を選択します。1台の VPS で self-hosted AFFiNE instance を運用する構成 は後者に該当します。どのコンテナも PUID を読み取らず、データベースディレクトリとアップロードファイルの所有権は、環境変数ブロック内の設定ではなくランタイムによって決まるためです。self-hosted Chatwoot support desk も同様です。Rails コンテナと Sidekiq worker が同じ uploads directory に書き込みますが、どちらも PUID を読み取りません。そのため、このディレクトリはイメージがすでに実行しているユーザーに合わせる必要があります。新しい stack でも事情は変わりません。したがって、チームの各メンバーに専用の sandbox 化された OneCLI agent を割り当てる構成では、各ユーザーの workspace directory と Postgres data directory は、各イメージがすでに実行しているユーザーの所有になります。これは user: と chown の問題であり、PUID の問題ではありません。Codex、Claude Code、Hermes の前段に単一の self-hosted API を置く構成では、entrypoint が複数の identity を管理します。コンテナは root として起動し、console と gateway は権限のないユーザーで実行し、各 agent session はそれぞれ専用のユーザーで実行されます。この構成には user: を追加しないでください。entrypoint がデータボリュームの所有権を管理します。
メディアスタックの例: コンテナ間で共有する 1 つのグループ
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 グループの GID を 13000 に変更します。これにより、abc はホストの media グループと同じ GID で書き込みます。このツリーにアクセスするすべての linuxserver コンテナは、それぞれの PUID を維持し、同じ PGID を共有します。完成したライブラリを読み取るだけの、チェーン下流のコンテナも対象です。Jellyfin 自身も含まれます。そのライブラリを歩き回れる 90 年代のビデオ店として提供する Halcyonのようなフロントエンドは、HALCYON_JELLYFIN_URL を使用して HTTP 経由で Jellyfin に接続します。標準的なデプロイでは、共有メディア PGID もメディアディレクトリのマウントも必要ありません。
次に、スタック内のすべての linuxserver コンテナで UMASK=002 を設定します。ここを見落とす人が多くいます。設定しない場合、別の要因で変更されない限り、アプリケーションは継承した umask を使い続けます。イメージの README に示されている例の値は UMASK=022 です。有効な umask が 022 の場合、アプリケーションが新しいファイルに 0666 を要求すると、デフォルト ACL がなければ 0644 になります。グループの他のメンバーは読み取れますが変更できず、設定した共有の大部分が機能しなくなります。002 でデフォルト ACL がない場合、アプリケーションが通常のファイル権限 0666 とディレクトリ権限 0777 を要求すると、0664 のファイルになります。また、setgid ツリー内では 2775 のディレクトリになり、グループが書き込めます。umask はビットを削除するだけです。そのため、アプリケーションが 0600 を要求すると 0600 になります。デフォルト ACL が設定されたディレクトリでは、umask は完全に無視されます。PUID と PGID の隣に変数を追加します。
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 ファイルに置きます。これにより、スタック全体が 1 つの定義を読み取ります。
PUID=1000
PGID=13000Compose はそのファイルを自動的に読み込み、${PUID} 形式の置換に使用します。これは認証情報で使用するものと同じ仕組みです。値を docker-compose.yml に書かず .env ファイルに置く運用もここに適用できます。ただし、この 2 つの数値は秘密情報ではありません。
設定をそのまま信頼せず、最初から最後まで検証してください。アプリケーションのユーザーとして、設定した umask を適用して新しいファイルを書き込み、ホストから読み取ります。まだ存在しないテスト用ファイル名を使用してください。
docker exec --user abc sonarr sh -c 'umask "$UMASK" && touch /data/downloads/permtest'
ls -ln /srv/media/downloads/permtest正常な結果では、所有者が PUID、グループが 13000、モードが -rw-rw-r-- になります。グループが 1000 と表示された場合は、ls -ldn /srv/media/downloads を実行します。ディレクトリに setgid ビット(drwxrwsr-x 内の s)がないか、ディレクトリ自体のグループがまだ 1000 で media になっていません。前述の chown と chmod 2775 コマンドで両方を修正できます。モードが -rw-r--r-- と表示された場合は、コンテナの UMASK 値とディレクトリのデフォルト ACL を確認してください。環境変数が古いままなら、コンテナを再作成します。このテストで確認しているのは、設定したマスクを適用して abc として作成した場合の動作です。アプリケーション自身の作成モードと有効な 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: を使うか、設定を変更しないことを前提としています。
小規模な単一アプリケーションの構成でも、同じ問題が発生します。そのため、セルフホストの openGym ワークアウトトラッカーを構築する場合は、bind mount を割り当てる前に、コンテナが実際にどのユーザーで実行されるかを確認してください。データベースを保存するディレクトリの所有権は、PUID を設定するかどうかにかかわらず、その設定を引き継ぐためです。リモートアクセスのリレーも同じ分類に入ります。独自の RustDesk リレーサーバーを実行する場合、初回起動時に hbbs が書き込む Ed25519 キーペアは、そのイメージが最終的に使用したユーザーの所有者として bind mount 内に作成されます。ホスト側で chown を実行すれば後から修正できます。また、user: キーを使って最初からユーザーを指定することもできます。このイメージは独自のユーザーを設定しないためです。ただし、mount したディレクトリが指定した ID で書き込み可能である必要があります。
後から追加するインフラストラクチャにも同じことが当てはまります。アプリの前段に Authentik を配置して単一ログインにする場合、PUID をまったく読み取らない公式の authentik イメージと Postgres イメージを実行します。ボリュームの所有権は、各プロセスが実行されるユーザーと、各イメージの entrypoint が行う初期設定によって決まります。Authentik は release 2025.10 で Redis への依存を削除しました。
そのため、プロジェクト間で環境変数のブロックをコピーする前に、各イメージの README を確認してください。Docker は、設定した環境変数を、その変数を内部で読み取る処理があるかどうかに関係なく、コンテナへ渡します。何も読み取らない PUID を設定しても、エラー、警告、効果は発生しません。コンテナは、Docker の user: 設定または Dockerfile で選択されたユーザーとして起動します。その後、entrypoint が権限を下げてからアプリケーションを起動する場合もあります。
新しいものをホストに追加する前に、この点を確認してください。セルフホストの open-kritt セキュリティスキャン基盤も例外ではありません。Compose ファイルを確認すれば、各イメージが PUID をサポートするか、mount するディレクトリの所有権がイメージ自体によって固定されているかを判断できます。
FAQ
Docker のファイルが 911:911 の所有になっているのはなぜですか?
911 は、linuxserver.io のイメージに組み込まれた abc ユーザーの UID と GID です。組み込みのデフォルト値と一致しているだけの場合もありますが、コンテナの PUID と PGID を変更した後も、ファイルにその所有権が残ることがあります。ホスト上にその ID に一致するアカウントがない場合、ls -l は数値をそのまま表示するため、表示する名前がありません。PUID と PGID を id の出力に設定し、docker compose up -d でコンテナを再作成してから、影響を受けるディレクトリで sudo chown -R 1000:1000 を実行して既存ファイルを修正します。
PUID と PGID はすべての Docker イメージで機能しますか?
いいえ。これらは Docker の機能ではなく、Docker が読み取ることもありません。イメージ自身の entrypoint がこれらを読み取り、アプリケーションの起動前に usermod と groupmod を呼び出す場合にだけ機能します。該当するのは linuxserver.io ファミリーと、この方式を採用した一部のプロジェクトです。プロジェクトによっては、paperless-ngx の USERMAP_UID や USERMAP_GID のように別の名前を使用します。どちらの変数も読み取らないイメージでは、変数は受け付けられますが、警告なしで無視されます。
Docker Compose では PUID と PGID と user: key のどちらを使用すべきですか?
イメージが対応している場合は PUID と PGID を使用します。entrypoint が root として動作する時間を確保できるため、/config を修正し、独自のサービスを正しく起動できます。選択したユーザーでの実行をイメージがサポートすると明記している場合は、user: を使用します。PUID に対応していないことだけを理由に、root なしで entrypoint が動作すると判断してはいけません。linuxserver イメージで user: を設定すると、PUID と PGID は機能しなくなり、Docker Mods とカスタムサービスの実行が停止します。また、すべてのマウントされたボリュームの権限管理が利用者の責任になります。
Sonarr に正しい PUID を設定しているのに、ファイルを移動できません。何が問題ですか?
次の 3 点を順番に確認します。まず、メディアのマウント自体です。ベースイメージのユーザー設定スクリプトは /app、/config、/defaults に対して chown を実行します。そのため、/data または /downloads を使用すると、ホスト側の所有権がそのまま維持されます。次に、共有グループです。メディアツリーにアクセスするコンテナに同じ PGID とグループ書き込み権限を設定します。最後に、umask です。有効なマスクが 022 の場合、アプリケーションが 0666 を要求し、デフォルト ACL が適用されなければ、0644 のファイルが作成されます。この場合、グループ内の他のコンテナは読み取れますが、変更できません。UMASK を未設定にしても、特定のマスクやファイルモードになるとは限りません。UMASK=002 を設定し、chmod 2775 でディレクトリに setgid ビットを設定すると、新しいファイルがグループを継承します。アプリケーションが 0600 を要求する場合は、プライベートファイルが作成されます。また、デフォルト ACL によって作成時の権限の割り当て方法が変わります。