# Laravel Dockerfile Generator

URL: /dockerizer/laravel

Generate a production Dockerfile for Laravel on FrankenPHP — OPcache tuned, config and routes cached at build time, running as www-data.

## Default configuration

- `appName` (App name): laravel — Used for the image tag, the compose service and the OCI labels.

- `port` (Port): 8080 — 8080 rather than 80 so the web server runs entirely as an unprivileged user. Publish it as 80 with `-p 80:8080`.

- `documentRoot` (Document root): public — The directory holding index.php, relative to the project root. Only this directory is web-reachable.

- `octane` (Laravel Octane): false — Keeps the framework booted between requests. Large speed win, but your code has to be free of per-request global state.

- `server` (Web server): frankenphp — FrankenPHP embeds PHP in a Caddy server: one process, HTTP/2 and HTTP/3, and worker mode if you want it. Apache with mod_php is the classic single-process alternative.

- `phpVersion` (PHP version): 8.4

- `phpExtensions` (PHP extensions): pdo_pgsql pdo_mysql intl zip gd opcache pcntl redis — Space-separated. Installed with install-php-extensions, which pulls the build dependencies and removes them again afterwards.

- `phpMemoryLimit` (memory_limit): 256M

- `phpUploadMaxFilesize` (upload_max_filesize): 32M

- `phpPostMaxSize` (post_max_size): 32M

- `phpMaxExecutionTime` (max_execution_time): 60

- `phpTimezone` (date.timezone): UTC

- `database` (Database service): none — Adds the database to docker-compose.yml with a healthcheck, a named volume and a DATABASE_URL wired into the app.

- `redis` (Redis service): false — Adds Redis to docker-compose.yml and exposes REDIS_URL to the app.

- `cacheMounts` (BuildKit cache mounts): true — Persists the package manager store between builds. Repeat builds skip the download entirely.

- `multiArch` (Multi-architecture build): false — Adds BUILDPLATFORM/TARGETARCH so `docker buildx build --platform linux/amd64,linux/arm64` cross-compiles natively.

- `healthcheck` (Healthcheck): true — Adds a HEALTHCHECK so orchestrators can restart an unresponsive container.

- `ociLabels` (OCI labels): false — Adds org.opencontainers.image.* metadata to the final image.

- `buildSecret` (Build secret mount): false — Reads a private registry token via --mount=type=secret so it never lands in an image layer.

## Generated files

### Dockerfile

Multi-stage build: dependencies, compilation and the runtime image are separate, so only what the app needs at runtime ships.

```docker
# syntax=docker/dockerfile:1
# Generated by Easypanel Dockerizer — https://easypanel.io/dockerizer

# --- Base ---------------------------------------------
FROM dunglas/frankenphp:1-php8.4-alpine AS base
WORKDIR /app

# install-php-extensions resolves the build dependencies for each extension and removes them afterwards
COPY --from=mlocati/php-extension-installer:latest /usr/bin/install-php-extensions /usr/local/bin/
RUN install-php-extensions pdo_pgsql pdo_mysql intl zip gd opcache pcntl redis

# Production php.ini, copied over the image defaults
COPY php.ini /usr/local/etc/php/conf.d/zz-app.ini

# --- Dependencies -------------------------------------
FROM base AS vendor
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

# Manifests only — application edits must not re-resolve the tree
COPY composer.json composer.lock* ./

# --no-scripts and --no-autoloader because the application is not in place yet
RUN --mount=type=cache,id=composer-cache,target=/root/.composer/cache,sharing=locked \
    composer install --no-dev --no-interaction --prefer-dist --no-progress --optimize-autoloader --no-scripts --no-autoloader

# --- Runtime ------------------------------------------
FROM base AS runtime
COPY Caddyfile /etc/frankenphp/Caddyfile

# Caddy keeps its certificate and config state here; www-data has to own it
RUN chown -R www-data:www-data /data /config

# Vendor tree first, so application edits do not invalidate it
COPY --from=vendor --chown=www-data:www-data /app/vendor ./vendor
COPY --chown=www-data:www-data . .
RUN --mount=type=bind,from=composer:2,source=/usr/bin/composer,target=/usr/bin/composer \
    composer dump-autoload --optimize --classmap-authoritative --no-dev

# Caching these at build time means no writes to disk on first request
RUN php artisan config:cache \
 && php artisan route:cache \
 && php artisan view:cache
USER www-data
EXPOSE 8080
STOPSIGNAL SIGTERM

# Lets Docker, Compose and Easypanel see when the app is wedged
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
  CMD ["php", "-r", "exit(@file_get_contents('http://127.0.0.1:8080/') === false ? 1 : 0);"]

# No CMD: the FrankenPHP image starts Caddy itself, and the Caddyfile above points it at /app/public.

```

### .dockerignore

Keeps the build context small and stops secrets and local dependencies from reaching an image layer.

```bash
# Version control
.git
.gitignore
.github

# Secrets — never bake these into an image layer
.env
.env.*
!.env.example
*.pem
*.key

# Editor and OS noise
.vscode
.idea
.DS_Store
Thumbs.db

# Docs and local tooling
README.md
LICENSE
docs
.editorconfig
docker-compose*.yml
Dockerfile*
.dockerignore

# Composer installs inside the image
vendor

# Framework caches and user uploads
storage/logs/*
storage/framework/cache/*
storage/framework/sessions/*
storage/framework/views/*
bootstrap/cache/*
var/cache
var/log

```

### php.ini

Production PHP settings with OPcache and JIT enabled, timestamp validation off and errors sent to stderr.

```ini
; Production php.ini overrides. Copied over the image defaults.
date.timezone = UTC
memory_limit = 256M
max_execution_time = 60
post_max_size = 32M
upload_max_filesize = 32M

expose_php = Off
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /dev/stderr

; OPcache — the single biggest PHP performance win in production.
opcache.enable = 1
opcache.enable_cli = 0
opcache.memory_consumption = 192
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 0
opcache.jit = tracing
opcache.jit_buffer_size = 64M

realpath_cache_size = 4096K
realpath_cache_ttl = 600

```

### Caddyfile

FrankenPHP's Caddy config — serves the front controller directly, with automatic compression and HTTP/2.

```nginx
{
	frankenphp
	order php_server before file_server
	auto_https off
}

:8080 {
	root * /app/public
	encode zstd br gzip

	# Refuse dotfiles outright — .env must never be reachable.
	@dotfiles path_regexp \/\.
	respond @dotfiles 404

	php_server {
		index index.php
	}

	header {
		X-Content-Type-Options nosniff
		X-Frame-Options SAMEORIGIN
		-Server
	}
}

```

### docker-compose.yml

Runs the image locally with its backing services, wired together and health-gated.

```yaml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    ports:
      - "8080:8080"

```

## Frequently asked questions

### Why cache config and routes during the build?

Because the image is immutable, so the cache can never go stale. It also means the container has nothing to write on first request, which is what lets it run with a read-only filesystem.

### Where do migrations run?

Not in the entrypoint — with more than one replica they race. Run `php artisan migrate --force` as a release step before the new version takes traffic.

### Is FrankenPHP production-ready for Laravel?

Yes. It is the runtime Laravel Octane recommends, and Laravel Sail ships it. You get HTTP/2, HTTP/3 and worker mode from a single process, with no FPM pool to size.

### Why is APP_KEY not in the image?

Because anyone who can pull the image could read it. Generate it once and pass it at runtime — a leaked APP_KEY lets an attacker forge sessions and decrypt anything you have encrypted.

## Related generators

- [Symfony](/dockerizer/symfony)

- [PHP](/dockerizer/php)

- [Node.js](/dockerizer/nodejs)

- [Medusa](/dockerizer/medusa)