Sign inSign up

jurriaanr/frankenphp

By jurriaanr

•Updated 26 days ago

Frankenphp with the extensions added used in personal projects

Image
Languages & frameworks
Web servers
0

304

jurriaanr/frankenphp repository overview

⁠jurriaanr/frankenphp

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.

⁠What's included

  • Matching PHP extensions from jurriaanr/php: bcmath, curl, exif, gd, intl, imagick, ldap, mbstring, memcached, mysqli, opcache, pdo_mysql, pdo_pgsql, simplexml, soap, sockets, redis, xmlrpc, xsl, zip
  • Locales: en_US, en_GB, de_DE, fr_FR, es_ES, nl_NL
  • Composer 2
  • Runtime utilities: git, ssh-client, zip, cron, nano
  • Europe/Amsterdam timezone
  • Security headers (see below)
  • Compression (zstd + gzip)
  • Static asset caching

⁠Environment variables

VariableDefaultDescription
FRANKENPHP_DOCUMENTROOT/app/publicWeb root
SERVER_NAME:80Caddy listen address
PHP_MAX_EXECUTION_TIME120Seconds before PHP kills a request
PHP_MEMORY_LIMIT256MPHP per-process memory limit
PHP_UPLOAD_LIMIT8Mupload_max_filesize + post_max_size
PHP_OPCACHE_LIMIT64Mopcache.memory_consumption
PHP_OPCACHE_REVALIDATE2opcache.revalidate_freq (seconds)
PHP_SESSION_SAVE_HANDLERfilessession.save_handler
PHP_SESSION_SAVE_PATH""session.save_path
PHP_SMTP_HOSTlocalhostPHP's SMTP ini directive
PHP_MAX_INPUT_VARS1000max_input_vars

⁠Usage

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:

⁠Overriding the Caddyfile

Mount a project-specific Caddyfile to replace the default:

  app:
    image: jurriaanr/frankenphp:latest
    volumes:
      - .:/app
      - ./Caddyfile:/etc/frankenphp/Caddyfile:ro

⁠Security headers

The default Caddyfile sets:

  • X-Clacks-Overhead: GNU Terry Pratchett (Not a security header)
  • X-Content-Type-Options: nosniff
  • X-XSS-Protection: 1; mode=block
  • X-Frame-Options: DENY
  • Referrer-Policy: strict-origin

Plus it strips Server and X-Powered-By.

⁠Request routing

Uses Caddy's php_server directive, which handles Symfony-style front-controller routing:

  1. If the requested path is a real file, serve it directly
  2. Otherwise, forward to index.php with the original URI preserved

This 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 and Vulcain integration

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.

⁠Known limitations

  • No FPM status endpoint. Healthchecks that hit /status (like php-fpm-healthcheck) won't work. The image's healthcheck hits the Caddy server root instead.
  • No supervisor. FrankenPHP supervises itself. Background tasks should use Symfony Messenger, a dedicated worker container, or the included cron.
  • Single-container tradeoff. Web server and PHP runtime can't be scaled independently. For most apps this is fine; for specialized workloads a traditional split may be more appropriate.

Tag summary

Content type

Image

Digest

sha256:dfaf99a6b…

Size

301.8 MB

Last updated

26 days ago

docker pull jurriaanr/frankenphp