WordPress runtime built on FrankenPHP
2.8K
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.
/var/www/index.php is missingwp-config.php creation or patching from MySQL environment variables/usr/local/bin/wpX-Forwarded-ProtoFORCE_HTTPS=trueFS_METHOD=direct8080Core extensions installed for WordPress:
bcmathbz2exifgd with FreeType, JPEG, WebP, and AVIF supportmysqlipdo_mysqlzipPECL extensions:
apcuimagickmemcachedrediswp / WP-CLI 2.12.0mysql clientcurlgitunzipCreate 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.
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
The entrypoint configures WordPress only when all four database variables are set:
| Variable | Required | Description |
|---|---|---|
MYSQL_DATABASE | Yes | WordPress database name |
MYSQL_USER | Yes | WordPress database user |
MYSQL_PASSWORD | Yes | WordPress database password |
MYSQL_HOST | Yes | MySQL hostname or service name |
Startup behavior:
/var/www/wp-config.php does not exist, it creates one with WP-CLI./var/www/wp-config.php already exists, it patches DB_NAME, DB_USER, DB_PASSWORD, and DB_HOST.FS_METHOD to direct so WordPress can write files without FTP prompts.| Variable | Default | Description |
|---|---|---|
SERVER_NAME | :8080 | Listen address used by FrankenPHP/Caddy |
WP_CRON_ENABLED | enabled | Set to false to disable the built-in background cron runner |
FORCE_HTTPS | disabled | Set to true, 1, yes, or on to force WordPress HTTPS detection |
Mount persistent storage at:
/var/www
This directory contains the complete WordPress installation, including:
wp-config.phpIf 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
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
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.
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 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
This image is the default WordPress container image in the wordpress-helm chart.
The chart deploys:
aapjeisbaas/wp-frankenphpInstall 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
docker build -t wp-frankenphp:local .
Run the local build:
docker run --rm -p 8080:8080 wp-frankenphp:local
The CI pipeline publishes this image to Docker Hub as aapjeisbaas/wp-frankenphp.
latest is published from the main branch.values.yaml./var/www.X-Forwarded-Proto: https from your reverse proxy, or set FORCE_HTTPS=true.WP_CRON_ENABLED=false when using an external scheduler./var/www.8080/tcp
This repository packages WordPress, PHP extensions, and FrankenPHP into a container image. Check the upstream projects for their respective licenses.
Content type
Image
Digest
sha256:d98bac85c…
Size
382.6 MB
Last updated
7 days ago
docker pull aapjeisbaas/wp-frankenphp