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 images と一部の他のイメージで使われている慣例です。そのため、これらを読み取るように作られていないイメージは、設定されていても無視します。
linuxserver.io image の内部には、abc というユーザーがあります。このユーザーは build 時に 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この出力は、コンテナが組み込みのデフォルト設定で実行されたことを示しています。この一覧の 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 を設定した後もこれらの行が 911 になっている場合、変数はコンテナに渡されていません。通常の原因は、docker-compose.yml を編集した後に docker compose restart を実行したことです。このコマンドは既存のコンテナを再利用するため、元の環境変数がそのまま使われます。環境変数を変更するには docker compose up -d が必要です。これによりコンテナが再作成されます。
コンテナが書き込んだファイルを削除できない理由
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 を設定すると、その後のアプリケーションの動作が変わります。すでにディスク上にあるファイルは、さかのぼって修正されません。スタックを停止し、自分で所有者を修正してから、再度起動します。
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 は起動時に、正確に次の 3 つのパスだけで chown を実行します。/app、/config、/defaults です。メディアのマウントはこの一覧に含まれません。/data、/downloads、/tv は変更されずにアプリケーションへ渡されます。そのため、これらのマウントのホスト側が、コンテナユーザーでは書き込めない所有者になっていると、コンテナは正常に起動し、バナーにも正しい UID が表示されますが、最初のインポートで失敗します。
これは正しい動作です。コンテナを起動するたびに、12 TB のメディアライブラリ全体へ再帰的な 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見た目上の副作用にも注意が必要です。数値の user:に対応するエントリがコンテナ内の /etc/passwdにないため、内部のツールは whoami: cannot find name for user ID 1000と報告します。ID 自体は有効で、ファイルアクセスも通常どおり機能します。失敗するのは名前の解決だけです。
Rootless Docker
Rootless Docker では、デーモン自体が非特権ユーザーとして実行されるため、ホスト上で実際の root として動作するものはありません。所有権の計算方法が完全に変わります。コンテナの UID 0 は、Rootless Docker を実行しているホストユーザーの UID にマッピングされます。コンテナの UID nについては、nが1以上の場合、subuid + (n - 1)にマッピングされます。ここで subuidは /etc/subuidに割り当てられた範囲の基点で、/etc/subgidです。Docker では、そこに少なくとも65,536個のサブ UID が必要です。
このマッピングは通常の助言を逆転させるため、もう一度確認してください。Rootless Docker では、root として書き込むコンテナが作成したファイルは、ホスト上では自分の所有になります。UID 1000 として書き込むコンテナが作成したファイルは、100999前後のサブ UID が所有することになり、シェルから操作できません。そのため、rootful daemon で正しい PUID 値が、ここでは誤った値になります。2つの仕組みは異なる層で同じ問題を解決するため、確認せずに重ねて使うと、削除に sudoが必要なディレクトリが作られます。Rootless に移行する場合は、ライブラリを移行する前に、自分のサーバーで書き込まれたファイル1つの所有権を確認してください。
単一の VPS 上で運用する多くのセルフホスト構成では、rootful daemon 上で PUID と PGID を使うのが現実的です。イメージがその方法を前提にビルドされ、ドキュメントにも記載されているためです。イメージの README にテスト済みと記載されている場合、または PUID をまったくサポートしない公式の upstream イメージを使う場合は、user:を選びます。1台の VPS 上でセルフホストする AFFiNE インスタンスのようなドキュメント用ワークスペースは後者に該当します。どのコンテナも PUID を読み取らず、データベースディレクトリとアップロードファイルの所有権は環境変数ブロック内の設定ではなく、ランタイムによって決まるためです。セルフホストする Chatwoot のサポートデスクも同じです。Rails コンテナと Sidekiq worker の両方が1つの uploads ディレクトリに書き込みますが、どちらも PUID を読み取りません。そのため、このディレクトリはイメージがすでに実行するユーザーに合わせる必要があります。新しい構成でも事情は変わりません。チームの各メンバーに独自のサンドボックス化された OneCLI agent を与える構成では、メンバーごとのワークスペースディレクトリと Postgres のデータディレクトリは、各イメージがすでに実行するユーザーの所有になります。そのため、問題は PUID ではなく user:と chownに関するものです。Codex、Claude Code、Hermes の前段に単一のセルフホスト API を置く構成でも同じです。そのイメージは組み込みの専用ユーザーとして実行され、データベースと保存済みキーを保持する 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 グループの GID が 13000 に変更されます。これにより、abc はホストの media グループと同じ GID で書き込みます。スタック内の各コンテナは固有の PUID を維持し、同じ PGID を共有します。これは、完成したライブラリを読み取るだけの下流コンテナにも当てはまります。Jellyfin 自体や、そのライブラリを歩き回れる 90 年代のビデオストアとして提供する Halcyon のように、Jellyfin に追加するフロントエンドも対象です。
次に、スタック内のすべての 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 ファイルに記述します。これにより、スタック全体で 1 つの定義を読み取れます。
PUID=1000
PGID=13000Compose は ${PUID} 形式の置換のために、このファイルを自動的に読み込みます。これは認証情報で使用する仕組みと同じです。値を docker-compose.yml から .env ファイルに移す運用もここに適用できます。ただし、この 2 つの数値は秘密情報ではありません。
設定を信用するだけでなく、最初から最後まで検証してください。1 つのコンテナ内からファイルを書き込み、ホストから読み取ります。
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: を使用するか、そのままにしておくことを前提としています。小規模な単一アプリケーションの構成でも同じ問題が生じます。self-hosted の openGym ワークアウトトラッカーを構築する場合は、bind mount を割り当てる前に、コンテナが実際にどのユーザーで実行されるかを確認してください。データベースを格納するディレクトリの所有権は、PUID を設定するかどうかにかかわらず、そのユーザー設定を引き継ぐためです。リモートアクセスのリレーも同じ分類に入ります。独自の RustDesk リレーサーバーを実行する場合、hbbs が初回起動時に書き込む Ed25519 キーペアは、そのイメージが最終的に使用するユーザーの所有者として bind mount に作成されます。利用できる修正手段は、ホスト側の chown だけです。後から追加するインフラも同様です。アプリの前段に Authentik を置いてシングルログインにする場合、PUID をまったく読み取らない公式の server、Postgres、Redis イメージを実行することになります。この場合、ボリュームの所有権は、設定可能な entrypoint ではなく runtime によって決まります。
そのため、プロジェクト間で環境変数のブロックをコピーする前に、各イメージの README を確認してください。Docker は、設定した環境変数を、その変数を内部で読み取るものがあるかどうかにかかわらず、任意のコンテナへ渡します。何も読み取らない PUID を設定しても、エラー、警告、効果は発生しません。コンテナは、その Dockerfile が最後に指定したユーザーで実行されます。実際のユーザーは、コンテナが書き込んだファイルの所有権から判断できます。サーバーに新しいものを追加する前に、この確認を済ませてください。self-hosted の open-kritt セキュリティスキャンスタックも例外ではありません。Compose ファイルを確認すれば、イメージが PUID に対応しているのか、mount するディレクトリの所有権がイメージ自体によって固定されているのかが分かります。
FAQ
Docker ファイルの所有者が 911:911 になるのはなぜですか?
911 は、linuxserver.io イメージに組み込まれた abc ユーザーの UID と GID です。これが表示される場合、コンテナが PUID と PGID を設定せずに起動したため、init スクリプトが組み込みのデフォルト値をそのまま使用しています。ホスト上に ID 911 のアカウントがないため、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: キーのどちらを使うべきですか?
イメージが対応している場合は PUID と PGID を使用してください。entrypoint が root として動作し、/config の修正や独自サービスの正常な起動に必要な処理を実行できるためです。イメージが PUID に対応していない場合、またはイメージの README に非 root 動作でテスト済みと記載されている場合は、user: を使用してください。linuxserver イメージで user: を設定すると、PUID と PGID は機能しなくなります。また、Docker Mods とカスタムサービスが実行されなくなり、マウントしたすべてのボリュームの権限管理を自分で行う必要があります。
Sonarr に正しい PUID を設定しているのに、ファイルを移動できません。何が問題ですか?
次の3点を順番に確認してください。まず、メディアのマウント自体を確認します。init は /app、/config、/defaults だけに chown を実行するため、/data または /downloads はホスト上の所有権をそのまま保持します。次に、共有グループを確認します。ダウンロードクライアントと Sonarr が異なる GID で動作していると、互いのファイルを変更できません。そのため、スタック内のすべてのコンテナに同じ PGID を設定してください。最後に、umask を確認します。イメージのデフォルト値 UMASK=022 では、グループの書き込みビットなしで 0644 としてファイルが作成されるため、共有グループが機能しません。UMASK=002 を設定し、chmod 2775 でディレクトリに setgid ビットを設定してください。これにより、新しいファイルがグループを継承します。