Docker base image for Laravel production applications using FrankenPHP
This repository provides a minimal yet comprehensive Docker base image for deploying your Laravel application in production. It is intended purely as a foundation image — you should extend it in your project rather than run it directly (see below).
- Development (
:dev
): Built automatically from every commit to themain
branch. - Production (
:vX.Y.Z
): Tagged versions for stable releases, with:latest
pointing to the most recent version.
- Release Tags
- Table of Contents
- Features
- Getting Started
- Environment Variables
- Container Modes
- Manual Setup vs Automatic Setup
- Testing Connections
- Contributing
- License
- FrankenPHP: Powered by the FrankenPHP runtime, providing a performant way to serve Laravel.
- Container Modes: Easily switch between
app
,worker
,horizon
, andscheduler
modes. - Connection Testing: Optional checks for database, cache, S3, and SMTP connections before serving the app.
- Automatic Setup: Laravel migrations, caches (config, routes, views, events), and storage linking happen by default.
- PHP Extensions: Commonly used PHP extensions for a typical Laravel application (bcmath, bz2, intl, redis, etc.).
Because this is a base image, you’ll typically reference it in your own Dockerfile
.
We strongly recommend using multi-stage builds to handle dependencies
(e.g., installing Composer or Node packages), ensuring your final production image is as
lean as possible.
Below is an example Dockerfile
that extends laravel-docker-base
:
FROM ghcr.io/kloudkit/laravel-docker-base:latest AS base
#################################### Vendor ####################################
FROM base AS vendor
WORKDIR /build
COPY composer.json .
COPY composer.lock .
COPY packages packages
RUN composer install \
--ignore-platform-reqs \
--no-cache \
--no-interaction \
--no-scripts \
--prefer-dist
#################################### NodeJS ####################################
FROM node:latest AS client
WORKDIR /build
COPY package*.json .
COPY postcss.config.js .
COPY tailwind.config.js .
COPY vite.config.js .
COPY resources resources
COPY --from=vendor /build/vendor vendor
RUN npm install && npm run build
##################################### App ######################################
FROM base
COPY --chown=laravel:laravel composer.json composer.json
COPY --chown=laravel:laravel composer.lock composer.lock
COPY --chown=laravel:laravel app app
COPY --chown=laravel:laravel bootstrap bootstrap
COPY --chown=laravel:laravel config config
COPY --chown=laravel:laravel database database
COPY --chown=laravel:laravel packages packages
COPY --chown=laravel:laravel public public
COPY --chown=laravel:laravel resources resources
COPY --chown=laravel:laravel routes routes
COPY --chown=laravel:laravel --from=client /build/public public
RUN composer install \
--no-ansi \
--no-dev \
--no-interaction \
--no-progress \
--no-scripts \
--prefer-dist \
--quiet \
&& composer dump-autoload \
--classmap-authoritative \
--no-dev
You can customize the container by setting the following environment variables:
Variable | Default | Modes | Description |
---|---|---|---|
APP_DEBUG |
false |
* |
Laravel debug mode |
APP_ENV |
"production" |
* |
Laravel environment name |
CONTAINER_MANUAL_SETUP |
(empty) | * |
Skips automatic setup (migrations, caching, etc.) |
CONTAINER_MODE |
"app" |
* |
Define the container mode (see Container Modes) |
CONTAINER_PORT |
8000 |
app |
Port FrankenPHP listens to (when in app mode) |
CONTAINER_WORKER_DELAY |
10 |
worker |
queue:work delay |
CONTAINER_WORKER_SLEEP |
5 |
worker |
queue:work sleep |
CONTAINER_WORKER_TRIES |
3 |
worker |
queue:work tries |
CONTAINER_WORKER_TIMEOUT |
300 |
worker |
queue:work timeout |
TEST_CACHE_CONNECTION |
true |
* |
Test cache connection on startup |
TEST_DB_CONNECTION |
true |
* |
Test database connection on startup |
TEST_S3_CONNECTION |
false |
* |
Test S3 connection on startup |
TEST_SMTP_CONNECTION |
false |
* |
Test SMTP connection on startup |
TEST_CONNECTION_TIMEOUT |
10 |
* |
Seconds to attempt each connection test before failing |
This image supports multiple container modes via the CONTAINER_MODE
variable, each
focusing on a specific type of service:
- Serves the Laravel application using FrankenPHP on port
CONTAINER_PORT
(default8000
). - Ideal for load-balanced or standalone app containers.
- Runs
php artisan queue:work
with the provided settings (CONTAINER_WORKER_*
). - Suitable for handling asynchronous jobs.
- Runs
php artisan horizon
. - Ideal if you prefer Laravel Horizon for managing queues.
- Runs
php artisan schedule:work
in the foreground. - Great for cron-like, scheduled tasks.
By default, this image automatically:
- Tests connections (DB, cache, S3, SMTP) if
TEST_*
variables are set totrue
. - Runs
php artisan migrate --force
. - Creates the storage symlink (if not already present).
- Caches config, events, routes, and views.
If you need to skip these steps (e.g., you manage migrations separately), set
CONTAINER_MANUAL_SETUP
to any non-empty value, and the container will skip all
auto-setup steps, running the Container Mode command directly.
The image's entrypoint can optionally test common connections before starting (or failing fast), depending on environment variables:
- Database:: Controlled by
TEST_DB_CONNECTION
(true
by default). - Cache:: Controlled by
TEST_CACHE_CONNECTION
(true
by default). - S3:: Controlled by
TEST_S3_CONNECTION
(false
by default). - SMTP:: Controlled by
TEST_SMTP_CONNECTION
(false
by default).
Each test will attempt a connection for up to TEST_CONNECTION_TIMEOUT
seconds before
giving up and exiting with a non-zero code.
This ensures your container won’t fully start unless its dependencies are actually ready.
Important
Ensure you provide the correct authentication environment variables (DB_HOST, DB_USERNAME, DB_PASSWORD, etc.) for any connections you enable.
Contributions are welcome! Please open issues or submit pull requests for any improvements or fixes you find.
This project is open-sourced software licensed under the MIT license. Feel free to adapt it to your needs.