Laravel Dockerfile: PHP-FPM 8.4 on Debian (Multi-Stage)

This Laravel Dockerfile builds the vendor directory in a composer:2 stage and runs the app on the Debian-based php:8.4-fpm image with pdo_mysql and OPcache. PHP-FPM runs as www-data on port 9000 behind your web server. Choose Debian over Alpine when extensions or libraries you need expect glibc.

CI status
Build passing
Image size (uncompressed)
527.9 MB
Build time
53 s
Runtime image
php:8.4-fpm
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 --no-dev --no-scripts

FROM php:8.4-fpm
RUN docker-php-ext-install pdo_mysql opcache
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

    Composer runs in its own official image, so the dependency manager never ends up in 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. Scripts and autoloading wait for the full source, and platform checks are skipped because the runtime stage provides the extensions.

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

    Builds an optimized class map once the application code is present, which makes class loading faster in production.

  4. FROM php:8.4-fpm

    The Debian variant of the official PHP-FPM image. It's larger than Alpine but compatible with libraries and extensions built for glibc.

  5. RUN docker-php-ext-install pdo_mysql opcache

    Compiles the MySQL PDO driver and OPcache with the image's helper script. OPcache keeps compiled PHP in memory between requests.

  6. COPY --from=vendor --chown=www-data:www-data /app ./

    Copies the app and vendor directory owned by www-data, so PHP-FPM can read code and write to storage and bootstrap/cache.

  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 laravel-app .
docker run --rm -p 9000:9000 laravel-app

Port 9000 speaks FastCGI rather than HTTP, so put Nginx or Caddy in front with a FastCGI pass to the container and your public directory as the document root.

Other Laravel Dockerfile variants

The Alpine variant produces a smaller image and suits most Laravel apps. Debian is the safer pick when an extension like imagick, a PDF tool or a vendor SDK depends on glibc. Compare the measured sizes of both in the table below.

VariantRuntime imageImage sizeCI
PHP-FPM 8.4 Alpine + Composer stagephp:8.4-fpm-alpine119.3 MBPassing
PHP-FPM 8.4 Debian + Composer stagephp:8.4-fpm527.9 MBPassing

Troubleshooting

could not find driver (SQL: select ...)
The PDO extension for your database is missing. Install pdo_mysql or pdo_pgsql in the runtime stage and rebuild the image.
docker-php-ext-install fails because a library header is missing
Some extensions need system packages first. Install their development headers with apt-get in the same RUN before building the extension.
Permission denied writing to storage/logs/laravel.log
storage and bootstrap/cache must be writable by www-data. Keep the chown on the COPY line and match ownership on any mounted volumes.

FAQ

Alpine or Debian for Laravel?
Alpine gives smaller images. Debian avoids musl compatibility issues with some extensions and third-party binaries, at the cost of a larger image.
Why are Composer scripts skipped?
Laravel's post-install scripts call artisan, which may need environment values that only exist at runtime. Run package discovery and caching in your deploy step.
How do I add Redis support?
Install the redis extension with pecl install redis followed by docker-php-ext-enable redis in the runtime stage, or use the Predis package instead.