Symfony Dockerfile: PHP-FPM 8.4 Alpine with Composer Stage

This Symfony Dockerfile installs production dependencies in a composer:2 stage with an authoritative class map, then runs the app on php:8.4-fpm-alpine with OPcache and APP_ENV set to prod. PHP-FPM runs as www-data on port 9000, so Nginx or Caddy sits in front to handle HTTP.

CI status
Build passing
Image size (uncompressed)
99 MB
Build time
108 s
Runtime image
php:8.4-fpm-alpine
Port
9000
Last built
2026-10-15
Dockerfile
# syntax=docker/dockerfile:1
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist --ignore-platform-reqs
COPY . .
RUN composer dump-autoload --optimize --classmap-authoritative --no-dev --no-scripts

FROM php:8.4-fpm-alpine
RUN docker-php-ext-install opcache
ENV APP_ENV=prod
WORKDIR /var/www/html
COPY --from=vendor --chown=www-data:www-data /app ./
USER www-data
EXPOSE 9000
CMD ["php-fpm"]
.dockerignore
.git
vendor/
node_modules/
.env
var/
storage/logs/*
storage/framework/cache/*
Dockerfile
.dockerignore

Before you build

How this Dockerfile works

  1. FROM composer:2 AS vendor

    Dependencies are installed in Composer's official image, so Composer itself never reaches the runtime image.

  2. RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist --ignore-platform-reqs

    Installs only production packages from the lockfile. Symfony's auto-scripts are skipped because they need the runtime environment.

  3. RUN composer dump-autoload --optimize --classmap-authoritative --no-dev --no-scripts

    An authoritative class map tells the autoloader never to search the filesystem for unknown classes, which speeds up every request in production.

  4. FROM php:8.4-fpm-alpine

    The official PHP-FPM image on Alpine serves PHP over FastCGI and keeps the runtime image small.

  5. RUN docker-php-ext-install opcache

    OPcache keeps compiled PHP in memory between requests. Add database drivers such as pdo_pgsql to this same line.

  6. ENV APP_ENV=prod

    Makes Symfony boot in production mode, with optimized caches and no debug toolbar, even if no .env file sets it.

  7. USER www-data EXPOSE 9000 CMD ["php-fpm"]

    Runs PHP-FPM as the web user and documents the FastCGI port your web server connects to.

Build and run

terminal
docker build -t symfony-app .
docker run --rm -p 9000:9000 symfony-app

Port 9000 speaks FastCGI rather than HTTP. Point Nginx or Caddy at it with Symfony's public directory as the document root, and pass secrets as environment variables.

Other Symfony Dockerfile variants

PHP-FPM behind a separate web server is the traditional Symfony production setup. FrankenPHP images that bundle the web server, or Debian-based images for glibc-dependent extensions, suit other platforms. Verified variants appear in the table below.

VariantRuntime imageImage sizeCI
PHP-FPM 8.4 Alpine + Composer stagephp:8.4-fpm-alpine99 MBPassing

Troubleshooting

Class not found errors after deploying
With an authoritative class map, classes added after the build aren't found. Make sure all code is copied before dump-autoload runs, then rebuild.
Environment variable not found: APP_SECRET
Symfony expects APP_SECRET at runtime. Pass it with -e or your orchestrator's secret store instead of committing it to .env.
Unable to write in the var/cache directory
var/ must be writable by www-data. Keep the chown on the COPY line, and match ownership on any mounted volumes.

FAQ

Why skip Symfony's auto-scripts during the build?
The auto-scripts clear and warm the cache using the runtime environment. Running cache:warmup at deploy time, with real environment variables, gives a correct cache.
Do I need a .env file in production?
No. Set real environment variables in your platform. Symfony reads them directly, and APP_ENV=prod is already set in the image.
Can I use FrankenPHP instead?
Yes. FrankenPHP bundles a web server and PHP in one image, which removes the separate Nginx container. It needs a different Dockerfile.