根本原因是宿主机与容器UID不一致导致挂载目录下临时文件归属错乱;需在docker-compose.yml中显式传递UID/GID,通过build.args或user字段对齐,并用chown -R $USER:$USER修复属主,禁用chmod 777。

根本原因不是容器没权限,而是宿主机与容器的 UID 不一致,导致挂载目录下临时文件(如 composer.lock、vendor/、~/.composer/cache/)归属错乱,操作系统直接拒绝写入。
docker-compose.yml 里必须显式传递 UID/GID
容器默认以 root(UID 0)运行,但宿主机项目目录通常属你自己的 UID(比如 1000),挂载后容器进程无法写入。光靠 volumes: - ./src:/var/www/html 不够,必须让容器内用户“长得像你”:
- 在
docker-compose.yml的 PHP 服务块中,用build.args或environment注入当前 UID/GID - 确保 Dockerfile 里有对应逻辑:用
ARG UID=1000+useradd -u ${UID} -m www+USER www - 如果用预构建镜像(如
php:8.3-apache),改用user: "${UID:-1000}:${GID:-1000}"直接覆盖启动用户
别在容器里跑 composer install,除非你控制了 UID
很多教程教你在 docker-compose exec php composer install,这非常危险——一旦容器 UID ≠ 宿主机 UID,生成的 vendor/ 文件在宿主机上就变成只读或属 root,后续 git 操作、IDE 索引、甚至 git status 都可能报错。
- 开发阶段:在宿主机执行
composer install(确保你已修复归属,见下一条) - CI/CD 或生产构建:把
composer install放进 Dockerfile 的RUN指令里,不挂载源码,避免权限穿透 - 真要容器内装:先确认
id -u和宿主机一致,再加--no-interaction --no-plugins减少临时目录创建点
ls -ld 报错路径才是真实问题目录
错误信息里带完整路径的那一行,就是你要修的地方。别猜,直接查归属:
-
file_put_contents(./vendor/autoload.php)→ 运行ls -ld vendor/,看第三列是不是root -
Could not write to /var/www/myapp/composer.lock→ls -ld composer.lock -
Writing cache file ~/.composer/cache/repo/...→ls -ld $(composer config --global cache-dir)
只要任意一个输出显示属主是 root,就立刻用 sudo chown -R $USER:$USER 修复,而不是 chmod -R 777 —— 后者不解决归属问题,还可能触发 Git 警告或 CI 拒收。
SELinux 或 overlayfs 场景下,:Z 或 :z 挂载选项不能少
在 CentOS/RHEL 或启用了 SELinux 的系统上,即使 UID 匹配,挂载目录仍可能因安全上下文被拒。错误典型如:PermissionError: [Errno 13] Permission denied: '/app/composer.lock'。
- 在
volumes条目末尾加:Z(私有卷)或:z(共享卷),例如:- ./src:/var/www/html:Z -
:Z会自动为宿主机文件打上container_file_t标签,允许容器进程访问 - 不用关 SELinux,也不用手动
chcon,:Z是 Docker 原生支持的最简解法
最易被忽略的是嵌套污染:一次 sudo composer install 可能让 vendor/composer/ 下某个子目录属 root,ls -la vendor/composer/ 才能发现,这时 chown -R 依然有效,但删掉重装更快。


















