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
# 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"].git
vendor/
node_modules/
.env
var/
storage/logs/*
storage/framework/cache/*
Dockerfile
.dockerignoreBefore you build
- Commit composer.lock so Composer installs exact versions.
- Put Nginx or Caddy in front of PHP-FPM on port 9000.
- Run config caching and migrations as deploy steps, not during the image build.
- Swap pdo_mysql for pdo_pgsql if your database is PostgreSQL.
How this Dockerfile works
FROM composer:2 AS vendorComposer runs in its own official image, so the dependency manager never ends up in the runtime image.
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist --ignore-platform-reqsInstalls 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.
RUN composer dump-autoload --optimize --no-dev --no-scriptsBuilds an optimized class map once the application code is present, which makes class loading faster in production.
FROM php:8.4-fpmThe Debian variant of the official PHP-FPM image. It's larger than Alpine but compatible with libraries and extensions built for glibc.
RUN docker-php-ext-install pdo_mysql opcacheCompiles the MySQL PDO driver and OPcache with the image's helper script. OPcache keeps compiled PHP in memory between requests.
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.
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
docker build -t laravel-app .
docker run --rm -p 9000:9000 laravel-appPort 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.
| Variant | Runtime image | Image size | CI |
|---|---|---|---|
| PHP-FPM 8.4 Alpine + Composer stage | php:8.4-fpm-alpine | 119.3 MB | Passing |
| PHP-FPM 8.4 Debian + Composer stage | php:8.4-fpm | 527.9 MB | Passing |
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.