Sign inSign up

aapjeisbaas/wp-frankenphp

By aapjeisbaas

Updated 7 days ago

WordPress runtime built on FrankenPHP

Image
0

2.8K

aapjeisbaas/wp-frankenphp repository overview

wp-frankenphp

Production-oriented WordPress runtime image built on top of FrankenPHP and PHP 8.4.

This image is published as:

aapjeisbaas/wp-frankenphp

It is designed to run WordPress from /var/www, listen on port 8080, and work well in container platforms where the database, persistent storage, reverse proxy, cache, and backups are managed outside the image.

The image is also the default WordPress runtime for the aapjeisbaas/wordpress-helm Helm chart.

Features

  • FrankenPHP with PHP 8.4
  • Automatic WordPress download when /var/www/index.php is missing
  • Automatic wp-config.php creation or patching from MySQL environment variables
  • WP-CLI included at /usr/local/bin/wp
  • WordPress-friendly PHP extensions included
  • Redis, APCu, Memcached, and Imagick PECL extensions included
  • HTTPS detection behind reverse proxies via X-Forwarded-Proto
  • Optional forced HTTPS mode with FORCE_HTTPS=true
  • Built-in WordPress cron runner, enabled by default
  • Direct filesystem writes for plugin/theme/core updates through FS_METHOD=direct
  • Runs HTTP on container port 8080

Included PHP Extensions

Core extensions installed for WordPress:

  • bcmath
  • bz2
  • exif
  • gd with FreeType, JPEG, WebP, and AVIF support
  • mysqli
  • pdo_mysql
  • zip

PECL extensions:

  • apcu
  • imagick
  • memcached
  • redis

Included Tools

  • wp / WP-CLI 2.12.0
  • mysql client
  • curl
  • git
  • unzip
  • ImageMagick

Quick Start With Docker

Create a Docker network:

docker network create wordpress

Start MySQL:

docker run -d \
  --name wordpress-mysql \
  --network wordpress \
  -e MYSQL_DATABASE=wordpress \
  -e MYSQL_USER=wordpress \
  -e MYSQL_PASSWORD=wordpress-password \
  -e MYSQL_ROOT_PASSWORD=root-password \
  -v wordpress-mysql:/var/lib/mysql \
  mysql:8

Start WordPress:

docker run -d \
  --name wordpress \
  --network wordpress \
  -p 8080:8080 \
  -e MYSQL_DATABASE=wordpress \
  -e MYSQL_USER=wordpress \
  -e MYSQL_PASSWORD=wordpress-password \
  -e MYSQL_HOST=wordpress-mysql \
  -v wordpress-files:/var/www \
  aapjeisbaas/wp-frankenphp:latest

Open:

http://localhost:8080

On first start, the container downloads the latest WordPress release into /var/www if no index.php exists. It then waits for MySQL, creates wp-config.php, and starts FrankenPHP.

Docker Compose Example

services:
  wordpress:
    image: aapjeisbaas/wp-frankenphp:latest
    ports:
      - "8080:8080"
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress-password
      MYSQL_HOST: mysql
    volumes:
      - wordpress-files:/var/www
    depends_on:
      - mysql

  mysql:
    image: mysql:8
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress-password
      MYSQL_ROOT_PASSWORD: root-password
    volumes:
      - mysql-data:/var/lib/mysql

volumes:
  wordpress-files:
  mysql-data:

Start it with:

docker compose up -d

Environment Variables

WordPress Database Setup

The entrypoint configures WordPress only when all four database variables are set:

VariableRequiredDescription
MYSQL_DATABASEYesWordPress database name
MYSQL_USERYesWordPress database user
MYSQL_PASSWORDYesWordPress database password
MYSQL_HOSTYesMySQL hostname or service name

Startup behavior:

  • The container tests the MySQL connection before writing config.
  • It retries the database connection 10 times with a 5 second delay.
  • If /var/www/wp-config.php does not exist, it creates one with WP-CLI.
  • If /var/www/wp-config.php already exists, it patches DB_NAME, DB_USER, DB_PASSWORD, and DB_HOST.
  • It sets FS_METHOD to direct so WordPress can write files without FTP prompts.
  • If the database variables are missing, database setup is skipped.
Runtime Options
VariableDefaultDescription
SERVER_NAME:8080Listen address used by FrankenPHP/Caddy
WP_CRON_ENABLEDenabledSet to false to disable the built-in background cron runner
FORCE_HTTPSdisabledSet to true, 1, yes, or on to force WordPress HTTPS detection

Volumes

Mount persistent storage at:

/var/www

This directory contains the complete WordPress installation, including:

  • WordPress core files
  • wp-config.php
  • plugins
  • themes
  • uploads

If the mounted directory is empty or does not contain /var/www/index.php, WordPress is downloaded automatically during startup.

Example bind mount:

docker run -p 8080:8080 \
  -v "$PWD/wordpress":/var/www \
  aapjeisbaas/wp-frankenphp:latest

HTTPS Behind A Reverse Proxy

The image includes an auto_prepend_file that normalizes HTTPS detection for WordPress.

When a proxy sends:

X-Forwarded-Proto: https

the image sets:

$_SERVER['HTTPS'] = 'on';
$_SERVER['SERVER_PORT'] = '443';

This keeps WordPress is_ssl() behavior correct behind load balancers, ingress controllers, Cloudflare, Traefik, Nginx, Varnish, or another reverse proxy.

If your proxy does not pass X-Forwarded-Proto, force HTTPS detection with:

-e FORCE_HTTPS=true

WordPress Cron

By default, the container starts a lightweight background loop that runs:

wp --path="/var/www/" --allow-root cron event run --due-now --no-color

approximately every 60 seconds with a small random offset.

Disable it when you use an external cron mechanism, such as Kubernetes CronJob, system cron, or a managed scheduler:

-e WP_CRON_ENABLED=false

For high-traffic or multi-replica deployments, prefer disabling the built-in runner and running cron from one dedicated scheduler.

PHP Defaults

The image ships /usr/local/etc/php/conf.d/99-custom.ini with these defaults:

upload_max_filesize=125M
post_max_size=125M
max_input_time=300
max_execution_time=300
memory_limit=256M
opcache.jit_buffer_size=50M
opcache.interned_strings_buffer=64
opcache.max_accelerated_files=100000
opcache.memory_consumption=128
opcache.save_comments=1
opcache.revalidate_freq=0
opcache.validate_timestamps=0
opcache.max_wasted_percentage=10

You can override these values by mounting another .ini file that loads later, for example:

docker run -p 8080:8080 \
  -v "$PWD/custom.ini":/usr/local/etc/php/conf.d/zz-custom.ini:ro \
  aapjeisbaas/wp-frankenphp:latest

Example custom.ini:

memory_limit=512M
upload_max_filesize=256M
post_max_size=256M

WP-CLI

WP-CLI is included and can be used with docker exec:

docker exec -it wordpress wp --allow-root --path=/var/www plugin list

Common examples:

docker exec -it wordpress wp --allow-root --path=/var/www core version
docker exec -it wordpress wp --allow-root --path=/var/www option get siteurl
docker exec -it wordpress wp --allow-root --path=/var/www cron event list

Change a site URL after moving domains:

docker exec -it wordpress wp --allow-root --path=/var/www search-replace \
  'https://old.example.com' \
  'https://new.example.com' \
  --skip-columns=guid

Kubernetes And Helm

This image is the default WordPress container image in the wordpress-helm chart.

The chart deploys:

  • WordPress using aapjeisbaas/wp-frankenphp
  • MySQL
  • Varnish
  • Memcached
  • Persistent volumes
  • Network policies
  • Optional S3-compatible backup and restore jobs
  • Optional horizontal pod autoscaling

Install the chart from Docker Hub as an OCI artifact:

helm install my-wordpress oci://registry-1.docker.io/aapjeisbaas/wordpress-helm

The chart defaults to:

wordpress:
  image:
    repository: aapjeisbaas/wp-frankenphp
    tag: v0.2.2
  containerPort: 8080
  dataPath: /var/www

Override the image tag during install or upgrade:

helm upgrade --install my-wordpress oci://registry-1.docker.io/aapjeisbaas/wordpress-helm \
  --set wordpress.image.tag=latest

The Helm chart passes the MySQL settings through Kubernetes Secrets and sets the required runtime options for FrankenPHP/Caddy. It also supports adding PHP settings through wordpress.php.customIni, mounted as /usr/local/etc/php/conf.d/zz-custom.ini.

Example:

wordpress:
  php:
    customIni: |
      memory_limit=512M
      upload_max_filesize=256M
      post_max_size=256M

For clusters where the storage class does not support ReadWriteMany, install with ReadWriteOnce:

helm install my-wordpress oci://registry-1.docker.io/aapjeisbaas/wordpress-helm \
  --set wordpress.persistence.accessMode=ReadWriteOnce \
  --set mysql.persistence.accessMode=ReadWriteOnce

Building Locally

docker build -t wp-frankenphp:local .

Run the local build:

docker run --rm -p 8080:8080 wp-frankenphp:local

Image Tags

The CI pipeline publishes this image to Docker Hub as aapjeisbaas/wp-frankenphp.

  • latest is published from the main branch.
  • Non-main branch builds are published with the branch name as the tag.
  • The Helm chart pins its own configured image tag in values.yaml.

Production Notes

  • Use persistent storage for /var/www.
  • Use a separate MySQL or MariaDB-compatible database service.
  • Put TLS termination in front of the container or Helm chart.
  • Pass X-Forwarded-Proto: https from your reverse proxy, or set FORCE_HTTPS=true.
  • Disable the built-in cron runner with WP_CRON_ENABLED=false when using an external scheduler.
  • For multiple WordPress replicas, use shared storage that supports your access mode and avoid running cron in every replica.
  • Back up both the database and /var/www.

Exposed Port

8080/tcp

License

This repository packages WordPress, PHP extensions, and FrankenPHP into a container image. Check the upstream projects for their respective licenses.

Tag summary

Content type

Image

Digest

sha256:d98bac85c

Size

382.6 MB

Last updated

7 days ago

docker pull aapjeisbaas/wp-frankenphp