ThinkPHP 容器化后写不出日志,通常是容器内用户身份与挂载目录权限不匹配,最稳妥的做法是在构建镜像时固定用户 ID 或在启动时修正挂载目录归属。
先说结论:优先在 Dockerfile 中设定正确的用户权限,或在容器启动入口脚本中动态修正 runtime 目录归属,避免直接使用 777 权限。
- 适合:使用 Docker 或 Docker Compose 部署 ThinkPHP 5/6/8 等项目
- 先准备:确认容器内运行用户(如 www-data)及主机挂载路径
- 验收:容器内能正常生成日志文件且无 Permission denied 报错
命令速用版
如果急需临时解决,可在容器启动后执行以下命令修正权限(假设用户为 www-data):
docker exec -it <容器 ID> chown -R www-data:www-data /var/www/html/runtime
chmod -R 775 /var/www/html/runtime
注意:容器重启后若未持久化修改,权限可能还原,建议写入 Dockerfile 或 entrypoint 脚本。
为什么会这样
Linux 系统通过用户 ID(UID)和组 ID(GID)控制文件读写。Docker 容器内的进程默认可能是 root,但为了安全通常切换到 www-data 等低权限用户。当主机目录挂载到容器时,如果主机侧目录归属与容器内用户 ID 不一致,容器内进程就没有写入权限。ThinkPHP 默认需要将日志写入 runtime/log 目录,权限不对就会报错。
分步处理
1. 确认容器运行用户
查看 Dockerfile 中是否有 USER 指令,或进入容器执行 id 命令。常见的是 www-data(UID 33)或自定义用户。
2. 方案 A:修改 Dockerfile(推荐)
在复制代码后、切换用户前,修正目录权限。示例片段:
COPY . /var/www/html
RUN chown -R www-data:www-data /var/www/html/runtime
USER www-data
3. 方案 B:Docker Compose 挂载配置
使用 Docker Compose 时,需在 docker-compose.yml 中正确配置 volumes,并配合入口脚本使用。
version: '3'
services:
web:
image: php:7.4-fpm
volumes:
- ./runtime:/var/www/html/runtime
entrypoint: /var/www/html/entrypoint.sh
command: php-fpm
4. 方案 C:入口脚本动态修正
编写 entrypoint.sh,在容器启动时执行 chown 命令,确保每次启动都修正权限。脚本内容如下:
#!/bin/sh
chown -R www-data:www-data /var/www/html/runtime
exec "$@"
记得给脚本执行权限:chmod +x entrypoint.sh。
5. 方案 D:主机侧预授权(需谨慎)
在主机上运行 docker-compose up 之前,先修改主机挂载目录的权限。注意不要硬编码 UID,建议先确认主机是否存在对应用户。
# 如果主机有 www-data 用户
chown -R $(id -u www-data):$(id -g www-data) ./runtime
# 如果主机没有该用户,需确认容器内 UID(通常为 33)
chown -R 33:33 ./runtime
怎么验证是否生效
1. 触发一个报错或日志记录行为(如访问一个不存在的页面)。
2. 进入容器查看日志文件是否生成:
docker exec -it <容器 ID> ls -l /var/www/html/runtime/log
3. 查看文件归属是否为容器运行用户,且最新修改时间是否为当前。
4. 实时查看日志更新:
docker exec -it <容器 ID> tail -f /var/www/html/runtime/log/error.log
常见坑
1. 直接使用 777 权限:虽然能解决问题,但在生产环境存在安全风险,任何用户都可写入,不建议长期使用。
2. UID 不一致:主机上的 UID 33 可能不对应 www-data,容器内却对应,挂载后权限混乱。建议统一规划 UID,或使用入口脚本在容器内修正。
3. SELinux 限制:如果主机开启 SELinux,即使 chmod 了也可能被拦截,可能需要设置 selinux 上下文或暂时关闭。
4. 重启失效:如果在容器内手动 chown 但未固化到镜像或启动脚本,容器重建后权限会还原。