PHP-FPM + nginx + supervisor runtime for Laravel apps. Multi-arch (amd64/arm64). No app code.
1.5K
Production runtime for Laravel applications: PHP-FPM + nginx + supervisor on Alpine Linux, ready to run the web server, queue workers and the scheduler from a single container.
The image contains no application code and no secrets. Your app's Dockerfile starts FROM it and only adds dependencies, compiled assets and source code, so builds take seconds instead of minutes.
linux/amd64, linux/arm64/var/www/html (docroot /var/www/html/public)Tags follow <exact runtime version>-r<revision>:
| Tag | Example | Moves? | Use it for |
|---|---|---|---|
<php>-r<n> | 8.4.26-r1 | Never (immutable) | Production — reproducible builds |
<php> | 8.4.26 | To the latest revision of that PHP version | Inspection |
<line> | 8.4 | To the latest build of the PHP line | Environments that want patches automatically |
latest | — | To the latest build of the newest PHP line | Quick tests only |
The revision (-r1, -r2, …) increases when the image is rebuilt without a PHP change (Alpine security patches, a new extension, config changes) and resets to -r1 with every new PHP release.
One line per PHP version, all with the same contents. Pick the one your app needs.
| Line | Immutable tag | Notes |
|---|---|---|
8.5 | 8.5.11-r1 | Newest line: latest points here |
8.4 | 8.4.26-r1 | |
8.2 | 8.2.34-r1 | |
7.4 | 7.4.33-r1 | Legacy: PHP 7.4 and Alpine 3.16 are end of life, with no PHP security fixes. Ships nginx 1.22. Use it only for apps that cannot move to PHP 8 yet |
| Component | Details |
|---|---|
| PHP | Official php:<version>-fpm-alpine, FPM on 127.0.0.1:9000 |
| Extensions | bcmath, exif, gd, intl, mbstring, opcache, pcntl, pdo_mysql, pdo_pgsql, redis, zip, plus the core ones (pdo_sqlite, curl, openssl, sodium, xml, …) |
| Tools | Composer 2, install-php-extensions, bash, curl, zip/unzip, postgresql-client |
| nginx | Port 8080, Laravel front controller, /health answered by nginx, dotfiles and project metadata denied |
| supervisor | php-fpm, nginx, N × queue:work, schedule:work, plus any /etc/supervisor/conf.d/*.conf |
| PHP config | /usr/local/etc/php/conf.d/zz-madlab.ini: 1 GB memory, 100 MB uploads, OPcache without timestamp validation, UTC, errors to stderr |
| Entrypoint | /usr/local/bin/madlab-entrypoint |
| Healthcheck | /usr/local/bin/madlab-healthcheck |
Not included: Node.js, git and compilers. Build your frontend assets in a separate stage.
# syntax=docker/dockerfile:1
ARG BASE_IMAGE=madlabcg/laravel-php:8.4.26-r1
# PHP dependencies
FROM ${BASE_IMAGE} AS vendor
COPY composer.json composer.lock ./
RUN composer install --no-interaction --no-scripts --no-autoloader --prefer-dist
# Frontend assets (architecture independent, always built natively)
FROM --platform=$BUILDPLATFORM node:22-alpine AS assets
WORKDIR /var/www/html
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
COPY --from=vendor /var/www/html/vendor ./vendor
RUN npm run build
# Application
FROM ${BASE_IMAGE}
ARG GIT_COMMIT=unknown
COPY --from=vendor --chown=www-data:www-data /var/www/html/vendor ./vendor
COPY --chown=www-data:www-data . .
COPY --from=assets --chown=www-data:www-data /var/www/html/public/build ./public/build
RUN composer dump-autoload --optimize --classmap-authoritative \
&& echo "${GIT_COMMIT}" > /.git-commit \
&& date -u +%Y%m%d-%H%M%S > /.build-date
Add a .dockerignore that excludes at least .git, .env*, node_modules, vendor, public/build and storage/* runtime files.
Run it:
docker run -d -p 8080:8080 \
-e APP_KEY=base64:... \
-e DB_CONNECTION=pgsql -e DB_HOST=db -e DB_DATABASE=app -e DB_USERNAME=app -e DB_PASSWORD=secret \
-e RUN_MIGRATIONS=true -e FORCE_MIGRATIONS=true \
my-laravel-app
On every container start madlab-entrypoint:
storage/ and bootstrap/cache directories, clears compiled views and fixes permissions for www-data.APP_KEY, runs storage:link and checks public/build/manifest.json.php artisan optimize when APP_ENV=production./docker-entrypoint-init.d/*.sh in alphabetical order.CMD (supervisor by default).| Variable | Default | Description |
|---|---|---|
APP_KEY | — | Required |
APP_ENV | production | Empty counts as production, which keeps destructive steps disabled |
DB_CONNECTION | pgsql | pgsql, mysql, mariadb or sqlite for the readiness check; other drivers are assumed ready |
WAIT_FOR_DB | true | false checks the database only once |
DB_WAIT_TIMEOUT | 60 | Seconds to wait for the database |
SKIP_DB_ON_FAIL | false | Start even if the database is unreachable (skips migrations) |
RUN_MIGRATIONS | — | true runs migrate --force on start |
FORCE_MIGRATIONS | — | Also required in production, on top of RUN_MIGRATIONS |
FRESH_INSTALL / FORCE_FRESH_INSTALL | — | Missing or empty database: migrate and seed. Migrated database: migrate:fresh --seed, never in production, once per container |
REQUIRE_VITE_MANIFEST | true | Set false for apps without Vite |
QUEUE_WORKER_ENABLE | true | Run queue workers under supervisor |
QUEUE_WORKER_PROCS | 4 | Number of workers |
QUEUE_WORKER_CONNECTION | $QUEUE_CONNECTION or database | Queue connection |
QUEUE_WORKER_QUEUE | default | Queues to listen on, e.g. high,default |
QUEUE_WORKER_SLEEP / _TRIES / _MAX_TIME / _MEMORY | 3 / 3 / 3600 / 512 | queue:work options |
SCHEDULER_ENABLE | true | Run schedule:work |
SENTRY_RELEASE | commit or build-<date> | Taken from /.git-commit or /.build-date when unset. An empty commit (a GIT_COMMIT build arg without a value) counts as unknown and falls back to the build date |
CHECK_DB | false | The healthcheck also tests the database connection |
HEALTHCHECK_PATH | / | Path requested by the healthcheck (must answer below 500) |
Boolean flags accept true, 1 or yes, tolerating quotes, spaces and inline # comments.
| Need | In your Dockerfile |
|---|---|
| Another PHP extension | RUN install-php-extensions imagick |
| A system package | RUN apk add --no-cache ghostscript |
| PHP settings | COPY php.ini /usr/local/etc/php/conf.d/zzz-app.ini |
| A custom nginx vhost | COPY nginx.conf /etc/nginx/http.d/default.conf |
| Extra processes (Horizon, Reverb…) | COPY horizon.conf /etc/supervisor/conf.d/horizon.conf (set QUEUE_WORKER_ENABLE=false if it replaces the workers) |
| Startup hooks | COPY docker/init/ /docker-entrypoint-init.d/ |
The image defines a HEALTHCHECK that verifies php-fpm and nginx are running and that the app answers below HTTP 500. In Compose:
healthcheck:
test: ["CMD", "/usr/local/bin/madlab-healthcheck"]
interval: 30s
timeout: 10s
retries: 3
start_period: 90s
Content type
Image
Digest
sha256:9217939a3…
Size
84.6 MB
Last updated
20 days ago
docker pull madlabcg/laravel-php