核心思路是让容器内进程的 uid/gid 与宿主机用户一致,通过 id -u/-g 获取并写入 .env,docker-compose.yml 中用 ${uid}:${gid} 动态设置 user,挂载时加 :delegated/:cached,composer 用 --uid/--gid 参数,且 .dockerignore 必须包含 vendor/。

核心思路是让容器内进程的 UID/GID 与宿主机用户一致,而不是强行修改文件权限或用 root 跑服务。真正有效的方案不依赖 chmod 777,也不靠反复 chown,而是从创建阶段就对齐所有权。
明确宿主机用户身份
先在项目根目录运行:
- id -u 查当前用户 UID(比如 1001)
- id -g 查当前用户 GID(比如 1001)
- 把这两个值写进 .env 文件:
UID=1001
GID=1001
在 docker-compose.yml 中注入用户身份
不要硬编码数字,用环境变量动态传递:
- 在 service 配置中加 user: "${UID:-1001}:${GID:-1001}"
- 挂载代码目录时,确保使用 :delegated(Mac)或 :cached(Linux)提升性能同时保持一致性
- 如果 PHP 或 Node 服务需写缓存、日志、vendor,务必让工作目录(如 /var/www/html 或 /app)由该 UID/GID 拥有
让 Composer 自动适配 UID/GID
Composer 2.2+ 支持运行时指定属主,比改镜像更轻量:
- 命令里加上 --uid=${UID} --gid=${GID}
- 例如:
command: composer install --no-dev --uid=${UID} --gid=${GID} - 这个参数只影响 vendor/、composer.lock 和缓存文件的归属,不影响进程本身 UID
避免 .dockerignore 漏掉 vendor/
这是最容易被忽略却最致命的一环:
- 如果 .dockerignore 没写 vendor/,每次 docker build 都会把宿主机上已存在的 vendor/ 复制进镜像
- 而宿主机 vendor/ 的 UID 很可能和构建环境不一致,导致镜像层内权限混乱
- 务必在 .dockerignore 第一行加上:
vendor/











