用 docker-compose.yml 启动 symfony 开发环境更实用,因为 symfony 需 web 服务器、数据库、缓存等多服务协同,单 dockerfile 硬塞所有组件会导致 https 缺失、静态资源 404、路由重写失效、日志混杂、重启失联、镜像重复构建等问题;docker-compose.yml 可清晰分离 app(php-fpm)、web(nginx)、db(postgresql/mysql)、redis 四服务,并通过 volume、env_file、时区与 opcache 显式配置保障热更新与环境一致性。

直接说结论:用 docker-compose.yml 启动 Symfony 开发环境比单写 Dockerfile 更实用,因为 Symfony 依赖 Web 服务器、数据库、缓存等多服务协同,硬塞进一个容器反而难调试、易崩。
为什么不能只靠 Dockerfile 跑 Symfony 开发环境
很多人一上来就写 Dockerfile 构建 PHP-FPM 镜像,然后 php -S 启动内置服务器 —— 这在开发阶段会踩三个坑:
-
php -S不支持 HTTPS、不处理静态资源(如public/build/下的 JS/CSS)、无法模拟真实 Nginx/Apache 的重写规则,路由和资产加载经常 404 - 没数据库容器时,
doctrine:database:create直接报错,而把 MySQL 塞进同一个容器会导致日志混杂、重启失联、无法单独扩缩 - 每次改代码都要 rebuild 镜像,
composer install又慢又重复;用 volume 映射又容易因权限或 autoloader 缓存导致类找不到
docker-compose.yml 必须包含的四个服务
一个可调试、可热更新、接近生产行为的 Symfony 开发栈,至少要这四块:
-
app:PHP-FPM 容器,只负责执行 PHP 逻辑,挂载源码 +var/cache卷(避免容器内缓存污染主机) -
web:Nginx 或 Caddy 容器,负责路由转发、静态文件服务、HTTPS 终止;配置必须包含try_files $uri /index.php?$query_string -
db:PostgreSQL 或 MySQL 容器,用initdb脚本自动导入 schema(避免手动doctrine:migrations:migrate) -
redis:用于 session 和 doctrine cache,Symfony 默认用cache.adapter.redis,不配它,APP_ENV=dev下也走 file cache,但行为和 prod 不一致
示例片段(关键字段):
services:
app:
build: .
volumes:
- .:/var/www/html:rw
- ./var/cache:/var/www/html/var/cache:rw
web:
image: nginx:alpine
ports: ["8000:80"]
volumes:
- .:/var/www/html:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
db:
image: postgres:15
environment:
POSTGRES_DB: symfony
POSTGRES_USER: symfony
POSTGRES_PASSWORD: changeit
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
APP_ENV=dev 在容器里怎么生效才不翻车
环境变量不是设了就完事。Symfony 容器化开发中,APP_ENV 错误最常引发两类问题:
- 路由缓存没清:dev 模式下应禁用路由缓存,但若
APP_ENV=dev只在docker-compose.yml的environment里设,而没透传给 PHP-FPM 子进程(比如没在www.conf中用env[APP_ENV]),就会沿用 prod 缓存,改路由不生效 - 敏感配置泄露:.env.local 里写了数据库密码,如果被 COPY 进镜像,build 后所有容器都带这个密钥 —— 正确做法是用
docker-compose.yml的env_file加载,且确保.env.local在.dockerignore里 - 时区错乱:
date.timezone必须在 PHP 容器里显式设(如ENV TZ=Asia/Shanghai),否则 Doctrine 的datetime字段入库时间偏移,debug bar 时间显示也不对
开发时改代码没反应?先查这三个地方
热更新失效不是 Symfony 的锅,而是容器配置细节没对齐:
-
opcache.enable=0必须在 PHP 容器里关闭,否则即使文件变了,opcode 还在内存里;别信 “dev 环境自动关”,Docker 里得手动关 - Linux 主机上用 WSL2 或 macOS 用 Docker Desktop 时,
inotify事件默认不穿透到容器,需在docker-compose.yml的app服务加sysctls: {fs.inotify.max_user_watches: "524288"} - Symfony 的
cache:warmup在 dev 模式下默认异步,但容器里没 supervisor 或 cron,warmup 失败无声无息 —— 改成cache:clear --no-warmup && cache:warmup才能立刻看到效果
真正麻烦的从来不是“能不能跑起来”,而是“改了代码,它到底有没有重新加载”。这点在容器里比本地更隐蔽,得盯着 docker logs -f app 和 var/log/dev.log 一起看。











