WindowsでDocker開発をしていると、アプリ自体は動いているのに、ファイル操作まわりで急に詰まることがあります。
たとえば、こういうやつです。
- PHPからログファイルを書けない
- アップロードファイルを保存できない
- コンテナ内で作ったファイルがホスト側で編集しづらい
- なぜかファイルの所有者が
rootになる - Dockerfileで
chownしたのに効かない -
chmodしたのに直らない - WSL / Windows / VSCode / Docker のどこから触ったかで挙動が変わる
特に Windows + WSL2 + Docker Desktop + VSCode の構成だと、見た目上は同じファイルを触っているつもりでも、実際には権限や所有者の見え方がズレることがあります。
Dockerの権限問題は、単なる permission denied ではなく、
ホスト側のファイル所有者
コンテナ内の実行ユーザー
bind mount / volume
Windows / WSL の境界
WebサーバーやPHPの実行ユーザー
あたりが全部絡みます。
この記事では、Docker環境でファイル操作できない時に見るべき場所を、原因別に整理します。
まず結論
Dockerの権限問題で最初に見るべきなのは、この4つです。
ls -ln
id
ps aux
docker inspect コンテナ名
ポイントは、ユーザー名ではなく UID/GIDを数字で見る ことです。
ls -ln
たとえばこう表示された場合、
drwxr-xr-x 5 1000 1000 4096 storage
この 1000:1000 が、実際に書き込むプロセスのUID/GIDと合っているかを見ます。
Dockerの権限問題は、最終的にはだいたいこの問いに戻ります。
このファイルの実体はどこにあるのか
誰が所有しているのか
どのプロセスが書き込もうとしているのか
そのプロセスはどのUID/GIDで動いているのか
よくあるパターンと対策
Dockerfileでchownしたのに効かない時
状況
Dockerfileでこうしているのに、
RUN chown -R www-data:www-data /var/www/html
実際にはPHPやApacheから書き込めない。
原因
docker-compose.yml で bind mount している可能性があります。
services:
app:
volumes:
- ./src:/var/www/html
この場合、コンテナ内の /var/www/html は、イメージ内のディレクトリではなく、ホスト側の ./src です。
つまり、Dockerfile内で chown しても、コンテナ起動時にホスト側ディレクトリで上書きされます。
Dockerfile内で /var/www/html をchown
↓
コンテナ起動時に ./src を /var/www/html にmount
↓
実際の所有者はホスト側 ./src に依存
対策
bind mountしている場合は、ホスト側の所有者を確認します。
ls -ln ./src
コンテナ側の実行ユーザーも確認します。
docker exec -it app id
docker exec -it app ps aux
Dockerfileの chown ではなく、ホスト側のUID/GIDと、コンテナ内の実行ユーザーを揃える必要があります。
Windows側とWSL側で触る場所が混ざっている時
状況
同じプロジェクトを触っているはずなのに、操作する場所によって挙動が変わる。
たとえば、以下が混ざっている状態です。
WSL内のLinuxパス:
/home/user/project
Windows側から見たWSLパス:
\\wsl.localhost\Ubuntu\home\user\project
Dockerコンテナ内:
/var/www/html
VSCode Remote WSL:
Linux側として編集
Windows側VSCode:
Windows側からUNCパスとして編集
原因
Windows、WSL、Docker Desktop、VSCodeの境界をまたいでいるためです。
見た目上は同じファイルでも、
Windows側から触ったファイル
WSL側から触ったファイル
コンテナ内プロセスが作ったファイル
で、所有者や権限の見え方がズレることがあります。
特に、Windows側のVSCodeから \\wsl$ や \\wsl.localhost 経由で開いていると、Gitやファイル権限の扱いが微妙にややこしくなることがあります。
対策
Docker開発では、基本的にプロジェクトをWSL側に置き、WSL内からVSCodeを開くのが安定しやすいです。
cd ~/project
code .
この形なら VSCode Remote WSL として開かれ、Linux側の権限体系で扱いやすくなります。
Windows × Docker で権限問題が多い場合、まずここを疑う価値があります。
コンテナ内で作ったファイルがroot所有になる時
状況
コンテナ内で生成されたファイルが、ホスト側で root 所有になってしまう。
ls -ln
-rw-r--r-- 1 0 0 1234 sample.log
原因
コンテナ内のプロセスが root でファイルを作っているためです。
bind mountしている場合、コンテナ内で作られたファイルはホスト側にもそのUID/GIDで現れます。
コンテナ内root UID 0
↓
bind mount先にファイル作成
↓
ホスト側でもUID 0所有に見える
Windows環境だとここがさらに分かりづらくなります。
ファイルはWindows側のエクスプローラーやVSCodeから見えていても、実際にはWSL上のLinuxファイルシステムにあり、LinuxのUID/GIDで管理されています。
対策
開発用コンテナでは、docker-compose.ymlで実行ユーザーを指定する方法があります。
services:
app:
user: "1000:1000"
.env にUID/GIDを置いておくと扱いやすいです。
UID=1000
GID=1000
services:
app:
user: "${UID}:${GID}"
ただし、user: を指定しても、ApacheやNginxなどのworker processが別ユーザーで動く場合があります。
そのため、最終的には必ずプロセスを確認します。
docker exec -it app ps aux
ユーザー名は同じなのに書き込めない時
状況
ホスト側にもコンテナ側にも user や www-data がいるのに、なぜか書き込めない。
原因
Linuxの権限は、ユーザー名ではなくUID/GIDで見ています。
たとえばホスト側ではこうだとします。
id
uid=1000(user) gid=1000(user)
一方、コンテナ内の www-data はこうかもしれません。
id www-data
uid=33(www-data) gid=33(www-data)
この場合、名前がそれっぽくても、UID/GIDが違うので別ユーザー扱いです。
対策
名前ではなく数字で確認します。
ls -ln
例:
drwxr-xr-x 5 1000 1000 4096 app
この 1000:1000 と、実際に書き込むプロセスのUID/GIDが合っているかを確認します。
PHPからログファイルが書けない時
状況
アプリは表示されるが、ログだけ書けない。
Permission denied
failed to open stream
原因
ログディレクトリの所有者と、PHPを実行しているユーザーが違う可能性があります。
たとえばログディレクトリがホスト側で 1000:1000 所有なのに、PHPがコンテナ内で www-data(33:33) として動いている場合です。
対策
まずログディレクトリを数字で確認します。
ls -ln storage/logs
PHPやApacheの実行ユーザーを確認します。
docker exec -it app ps aux
Apacheの場合は、以下も確認します。
docker exec -it app cat /etc/apache2/envvars
APACHE_RUN_USER=www-data
APACHE_RUN_GROUP=www-data
実行ユーザーとログディレクトリの所有者がズレているなら、どちらかを揃えます。
開発環境なら、一時的に以下で切り分けることはあります。
chmod -R 777 storage/logs
ただし、これは恒久対応ではなく、原因確認用に留めた方がよいです。
Apacheの実行ユーザーが想定と違う時
状況
docker-compose.ymlで user: を指定したのに、Apacheから書き込めない。
services:
app:
user: "1000:1000"
原因
Apacheは、起動プロセスとworker processでユーザーが違うことがあります。
たとえば、起動はrootでも、実際のリクエスト処理は www-data で動くことがあります。
master process: root
worker process: www-data
この場合、docker exec で入った時のユーザーだけ見ても不十分です。
対策
実際のApacheプロセスを確認します。
docker exec -it app ps aux
Apacheの環境変数も確認します。
docker exec -it app cat /etc/apache2/envvars
必要であれば、docker-compose.ymlでApacheの実行ユーザーを指定します。
services:
app:
environment:
APACHE_RUN_USER: appuser
APACHE_RUN_GROUP: appgroup
ただし、イメージによってはこれだけでは反映されない場合があります。
その場合は、実際に読まれているApache設定を確認する方が確実です。
Nginxのcacheや一時ディレクトリだけ書けない時
状況
Nginx自体は起動しているのに、cacheや一時ディレクトリで権限エラーになる。
permission denied
/var/cache/nginx
/client_temp
/proxy_temp
原因
Nginxのworker processの実行ユーザーと、cacheディレクトリの所有者が合っていない可能性があります。
Nginxでは、master processはroot、worker processはnginxユーザー、という構成もあります。
master process: root
worker process: nginx
rootでコンテナに入って確認して「書ける」と思っても、worker processからは書けないことがあります。
対策
worker processのユーザーを確認します。
docker exec -it nginx ps aux
対象ディレクトリの所有者を確認します。
docker exec -it nginx ls -ln /var/cache/nginx
worker processのUID/GIDと、ディレクトリの所有者またはグループ権限を合わせます。
named volumeとbind mountを混同している時
状況
ファイルがどこにあるのか分からない。
ホスト側を変更してもコンテナ内に反映されない。
または、コンテナ内にあるのにホスト側で見つからない。
原因
bind mountとnamed volumeの違いを混同している可能性があります。
bind mount:
volumes:
- ./src:/var/www/html
これはホスト側の ./src をコンテナ内に見せています。
named volume:
volumes:
- db_data:/var/lib/mysql
これはDockerが管理するvolumeをコンテナ内に見せています。
対策
mount状態を確認します。
docker inspect コンテナ名
Mounts に以下のように出ます。
Type: bind
Source: /home/user/project/src
Destination: /var/www/html
または、
Type: volume
Name: project_db_data
Destination: /var/lib/mysql
named volumeの実体を確認する場合は以下です。
docker volume ls
docker volume inspect project_db_data
Composeでは、volume名にプロジェクト名のprefixが付くことがあります。
db_data
と書いていても、実際には
project_db_data
のような名前になっていることがあります。
既存volumeがroot所有で残っている時
状況
docker-compose.ymlやDockerfileを直したのに、まだ権限エラーが出る。
原因
初回起動時にroot所有で作られたファイルやディレクトリが、volume内に残っている可能性があります。
Dockerfileやcompose設定を後から直しても、既存volumeの中身は自動では直りません。
初回起動時にrootで作成
↓
volume内にroot所有で残る
↓
あとからuserを変えても書けない
対策
bind mountなら、ホスト側で所有者を直します。
sudo chown -R 1000:1000 ./data
named volumeなら、一時コンテナで直す方法があります。
docker run --rm \
-v project_data:/data \
alpine \
chown -R 1000:1000 /data
ただし、MySQLなどのDBデータディレクトリに対して雑に chown -R するのは危険です。
DB系は、対象ユーザーと公式イメージの仕様を確認してから作業した方がよいです。
MySQLコンテナが権限エラーで起動しない時
状況
MySQLコンテナが起動しない。
または、起動してすぐ落ちる。
ログに以下のような内容が出ることがあります。
Permission denied
Can't create/write to file
原因
/var/lib/mysql の所有者が、MySQLコンテナ内の mysql ユーザーと合っていない可能性があります。
bind mountしている場合は、ホスト側ディレクトリの所有者が重要です。
services:
mysql:
image: mysql:5.7
volumes:
- ./mysql_data:/var/lib/mysql
対策
まずログを確認します。
docker logs mysql
ホスト側の所有者を確認します。
ls -ln ./mysql_data
MySQLイメージ内の mysql ユーザーのUID/GIDを確認します。
docker run --rm mysql:5.7 id mysql
必要なら、ホスト側の所有者を合わせます。
sudo chown -R 999:999 ./mysql_data
ただし、既存DBに対して行う場合は、必ずバックアップを取ってから実行した方が安全です。
chmod 777で直るが、それでいいのか迷う時
状況
chmod -R 777 すると直る。
chmod -R 777 storage
原因
本当の原因は、UID/GIDや実行ユーザーのズレです。
777 はそれを解決しているのではなく、誰でも書けるようにして通しているだけです。
原因: 実行ユーザーと所有者がズレている
対処: chmod 777
結果: 書けるが、設計上のズレは残る
対策
開発環境での一時的な切り分けなら使うことはあります。
ただし、恒久対応としては以下の順で考えた方がよいです。
1. UID/GIDを揃える
2. 実行ユーザーを確認する
3. 書き込み先ディレクトリを限定する
4. group権限で775/664にする
5. 必要ならACLを使う
6. 777は最後の一時回避にする
確認コマンドまとめ
所有者を数字で見る
ls -ln
現在のユーザーを見る
id
コンテナ内のユーザーを見る
docker exec -it app id
docker exec -it app id www-data
実際のプロセスを見る
docker exec -it app ps aux
mount状態を見る
docker inspect app
volumeを見る
docker volume ls
docker volume inspect volume_name
Apacheの実行ユーザー設定を見る
docker exec -it app cat /etc/apache2/envvars
コンテナ起動プロセスの環境変数を見る
docker exec -it app sh -c "cat /proc/1/environ | tr '\0' '\n'"
まとめ
Windows × Docker、特に WSL2 + Docker Desktop + VSCode の構成では、Dockerの権限問題がかなり見えづらくなります。
表面的には、どれも同じように見えます。
Permission denied
書けない
消せない
編集できない
所有者がrootになる
しかし、原因はだいたい次のどれかです。
- bind mountでDockerfileのchownが効いていない
- ホスト側とコンテナ側のUID/GIDが違う
- Windows側とWSL側でファイルの触り方が混ざっている
- Apache/Nginx/PHP/MySQLの実行ユーザーが想定と違う
- named volumeとbind mountを混同している
- 既存volumeがroot所有で残っている
まず見るべきコマンドはこれです。
ls -ln
id
ps aux
docker inspect コンテナ名
Dockerの権限問題は、Docker特有の魔法というより、LinuxのUID/GID問題が bind mount や WSL によって見えづらくなっているだけです。
最終的には、常にこの問いに戻ります。
このファイルの実体はどこにあり、
誰が所有していて、
どのプロセスが、
どのUID/GIDで書き込もうとしているのか?
ここが見えるようになると、Dockerの permission denied はかなり落ち着いて切り分けできます。