Frankenphp with the extensions added used in personal projects
304
Custom FrankenPHP image — a modern PHP application server with Caddy as the HTTP layer and PHP embedded in the same process. No separate web server, no FPM sidecar.
Based on the official dunglas/frankenphp image — see Dockerfile for the pinned version.
This is the standard mode image: PHP executes per request, similar to traditional FPM behavior but without the FastCGI hop. For persistent-memory worker mode, see jurriaanr/frankenphp-worker.
jurriaanr/php: bcmath, curl, exif, gd, intl, imagick, ldap, mbstring, memcached, mysqli, opcache, pdo_mysql, pdo_pgsql, simplexml, soap, sockets, redis, xmlrpc, xsl, zipen_US, en_GB, de_DE, fr_FR, es_ES, nl_NLgit, ssh-client, zip, cron, nano| Variable | Default | Description |
|---|---|---|
FRANKENPHP_DOCUMENTROOT | /app/public | Web root |
SERVER_NAME | :80 | Caddy listen address |
PHP_MAX_EXECUTION_TIME | 120 | Seconds before PHP kills a request |
PHP_MEMORY_LIMIT | 256M | PHP per-process memory limit |
PHP_UPLOAD_LIMIT | 8M | upload_max_filesize + post_max_size |
PHP_OPCACHE_LIMIT | 64M | opcache.memory_consumption |
PHP_OPCACHE_REVALIDATE | 2 | opcache.revalidate_freq (seconds) |
PHP_SESSION_SAVE_HANDLER | files | session.save_handler |
PHP_SESSION_SAVE_PATH | "" | session.save_path |
PHP_SMTP_HOST | localhost | PHP's SMTP ini directive |
PHP_MAX_INPUT_VARS | 1000 | max_input_vars |
Mount your application at /app with the entry point at public/index.php:
services:
app:
image: jurriaanr/frankenphp:latest
ports:
- "80:80"
volumes:
- .:/app
No separate PHP service is needed — FrankenPHP is the runtime.
For HTTPS with automatic certificates, change SERVER_NAME to a real hostname and expose port 443:
app:
image: jurriaanr/frankenphp:latest
environment:
SERVER_NAME: example.com
ports:
- "80:80"
- "443:443"
volumes:
- .:/app
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
Mount a project-specific Caddyfile to replace the default:
app:
image: jurriaanr/frankenphp:latest
volumes:
- .:/app
- ./Caddyfile:/etc/frankenphp/Caddyfile:ro
The default Caddyfile sets:
X-Clacks-Overhead: GNU Terry Pratchett (Not a security header)X-Content-Type-Options: nosniffX-XSS-Protection: 1; mode=blockX-Frame-Options: DENYReferrer-Policy: strict-originPlus it strips Server and X-Powered-By.
Uses Caddy's php_server directive, which handles Symfony-style front-controller routing:
index.php with the original URI preservedThis matches Symfony, Laravel, Slim, and general PSR-7 framework conventions. Framework-level 404s are handled by PHP; only upstream failures (PHP crashed) trigger the built-in 503 page.
Mercure (real-time updates) and Vulcain (HTTP/2 Server Push for REST) run as separate containers:
services:
app:
image: jurriaanr/frankenphp:latest
volumes:
- .:/app
mercure:
image: dunglas/mercure
environment:
SERVER_NAME: ":80"
MERCURE_PUBLISHER_JWT_KEY: "${MERCURE_JWT_KEY}"
MERCURE_SUBSCRIBER_JWT_KEY: "${MERCURE_JWT_KEY}"
ports:
- "3000:80"
vulcain:
image: dunglas/vulcain
environment:
UPSTREAM: http://app
ports:
- "4000:80"
No special configuration is needed on the FrankenPHP side — the app publishes to Mercure via HTTP and Vulcain proxies to it like any upstream.
/status (like php-fpm-healthcheck) won't work. The image's healthcheck hits the Caddy server root instead.cron.Content type
Image
Digest
sha256:dfaf99a6b…
Size
301.8 MB
Last updated
26 days ago
docker pull jurriaanr/frankenphp