Symfony is a mature PHP framework that rewards a disciplined deploy. The difference between a slow, fragile Symfony deployment and a fast one usually comes down to three things: building the production cache ahead of time, running Doctrine migrations as a discrete step, and configuring OPcache. Let's cover all of it.
The production environment
Symfony behaves very differently in prod versus dev. Set the environment explicitly and disable debug:
APP_ENV=prod
APP_DEBUG=0Then install only production dependencies and dump an optimized autoloader:
composer install --no-dev --optimize-autoloader --classmap-authoritativeWarming the cache
In prod, Symfony compiles the service container, routing, and (optionally) Doctrine proxies to disk. Build that cache during your image build, not on the first request:
php bin/console cache:clear --env=prod
php bin/console cache:warmup --env=prodIf you also build front-end assets with AssetMapper or Webpack Encore, run that in the same build step so the runtime image ships ready to serve.
Doctrine migrations
Apply schema changes with a non-interactive migration command, run as its own deploy step:
php bin/console doctrine:migrations:migrate --no-interactionAs with any framework, keep migrations backward-compatible for one release so old and new instances can run side by side during a rolling deploy. Avoid doctrine:schema:update --force in production — generate proper migration files instead so changes are reviewable and repeatable.
Serving PHP in a container
Symfony runs as PHP-FPM behind a web server. The cleanest container pattern is FrankenPHP or a PHP-FPM image plus Caddy/Nginx. A FrankenPHP-based Dockerfile is compact:
FROM dunglas/frankenphp:php8.3
ENV APP_ENV=prod APP_DEBUG=0
WORKDIR /app
COPY composer.* ./
RUN composer install --no-dev --no-scripts --optimize-autoloader
COPY . .
RUN composer dump-autoload --classmap-authoritative \
&& php bin/console cache:warmup --env=prod
EXPOSE 80FrankenPHP serves HTTP directly and keeps the framework booted in memory, which removes per-request bootstrap overhead.
OPcache: the single biggest win
Without OPcache, PHP recompiles your scripts on every request. Enable and size it:
; php.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0Setting validate_timestamps=0 means PHP never checks the filesystem for changes — perfect for immutable container images, since you build a fresh image per deploy anyway.
Environment configuration
Symfony reads DATABASE_URL natively, which makes it a great fit for managed-database platforms:
DATABASE_URL="postgresql://user:pass@host:5432/dbname?serverVersion=16&charset=utf8"Keep secrets out of the repo. Use platform environment variables, or Symfony's encrypted secrets vault for values you want versioned.
Deploying on PandaStack
PandaStack supports any Dockerfile, which is the right path for PHP since you control the FPM/server image:
- 1Commit the Dockerfile above to your repo.
- 2Connect the repo as a container app in the [dashboard](https://dashboard.pandastack.io).
- 3PandaStack builds the image with rootless BuildKit in an ephemeral Kubernetes Job and deploys it via Helm with automatic SSL.
- 4Provision a managed PostgreSQL or MySQL database —
DATABASE_URLis injected automatically, and because Symfony reads it natively, no config change is needed. - 5Add a deploy step running
doctrine:migrations:migrate --no-interaction.
Build checklist
| Task | Command | Stage |
|---|---|---|
| Prod dependencies | composer install --no-dev | Build |
| Autoloader | composer dump-autoload --classmap-authoritative | Build |
| Warm cache | cache:warmup --env=prod | Build |
| Migrate DB | doctrine:migrations:migrate | Release |
| OPcache | validate_timestamps=0 | Runtime config |
References
- [Symfony: Deploying to production](https://symfony.com/doc/current/deployment.html)
- [Symfony performance and OPcache](https://symfony.com/doc/current/performance.html)
- [Doctrine Migrations documentation](https://www.doctrine-project.org/projects/doctrine-migrations/en/current/index.html)
- [FrankenPHP documentation](https://frankenphp.dev/docs/)
Deploying Symfony without babysitting servers is the goal. PandaStack's free tier runs container apps with a managed database and automatic SSL — try it at [dashboard.pandastack.io](https://dashboard.pandastack.io).