WordPress installed on Alpine Linux, with auto-generated configuration.
100K+
Docker Hub - Quay.io - bfren ecosystem
WordPress, with the core files kept read-only inside the image, and everything that changes - themes, plugins, uploads and wp-config.php - in one volume.
It is Nginx + PHP with:
wp-config.php generated from environment variables, with secret keys downloaded once and keptIt is designed to run behind a reverse proxy that handles SSL, such as Caddy Proxy.
With a MariaDB database (e.g. bfren/mariadb):
services:
wordpress:
image: quay.io/bfren/wordpress:php8.4-7.2
environment:
- BF_WP_DB_HOST=db
- BF_WP_DB_NAME=wordpress
- BF_WP_DB_USER=wordpress
- BF_WP_DB_PASS=change-me
ports:
- "8080:80"
volumes:
- ./v/wp-content:/wp-content
Open the site and follow WordPress's installation (site title and admin user).
wp-config.php generated from environment variables at every startImages are published to Docker Hub (bfren/wordpress) and Quay.io (quay.io/bfren/wordpress) for linux/amd64 and linux/arm64. Tags start with the PHP version, and can include the WordPress version.
| Tag | Example | Use when |
|---|---|---|
phpX.Y | php8.4 | You want the latest image for a PHP version |
phpX.Y-MAJOR | php8.4-7 | You want updates, but not breaking changes |
phpX.Y-MINOR | php8.4-7.2 | You want fixes only - recommended |
phpX.Y-VERSION | php8.4-7.2.0 | You want to pin this image exactly |
phpX.Y-wpA.B | php8.4-wp7.1 | You want to stay on a WordPress version |
phpX.Y-wpA.B.C | php8.4-wp7.1.3 | You want an exact WordPress release |
…-VERSION versions of both | php8.4-wp7.1.3-7.2.0 | You want to pin both |
phpX.Y.Z | php8.4.26 | You want an exact PHP release |
There is no latest tag. PHP 8.1 and older no longer receive security fixes - see Nginx + PHP for support dates.
| Port | Purpose |
|---|---|
80 | HTTP - put a reverse proxy in front of it for SSL. |
| Volume | Purpose |
|---|---|
/wp-content | Themes, plugins, uploads and languages, plus wp-config.php and salt (the secret keys). Back it up. |
| Variable | Values | Description | Default |
|---|---|---|---|
BF_WP_DB_HOST | host name | Required. Database server. | blank |
BF_WP_DB_NAME | text | Required. Database name. | blank |
BF_WP_DB_USER | text | Required. Database user. | blank |
BF_WP_DB_PASS | text | Required. Database password. | blank |
BF_WP_DB_PREFIX | text | Table prefix - an underscore is added (e.g. wp_). | wp |
BF_WP_DB_CHARSET | text | Database character set. | utf8mb4 |
BF_WP_DB_COLLATE | text | Database collation - leave blank unless you know you need it. | blank |
BF_WP_DEBUG | true, false | WordPress debug mode. | false |
BF_WP_CONFIG_REGENERATE | 0, 1 | 1 generates wp-config.php at every start. 0 only generates it if it doesn't exist, so you can edit it yourself. | 1 |
BF_WP_CONFIG_HARDEN | 0, 1 | 1 makes wp-config.php read-only to WordPress. 0 makes it writable, for plugins that need to change it. | 1 |
BF_WP_CONFIG_ADDITIONAL | PHP code | Extra lines added to wp-config.php, e.g. define( 'WP_MEMORY_LIMIT', '256M' );. | blank |
BF_WP_ENABLE_HTTPS_BEHIND_PROXY | 0, 1 | Treat requests as HTTPS when the proxy sends X-Forwarded-Proto: https. | 1 |
BF_WP_USE_SYSTEM_CRON | 0, 1 | Run scheduled tasks from the system's cron every minute, instead of during page loads. | 1 |
BF_WP_DISABLE_MAJOR_UPDATES | 0, 1 | Limit WordPress's automatic core updates to minor releases. | 1 |
BF_WP_INSTALL_WEBAUTHN | 0, 1 | Install the WP-WebAuthn plugin (you still activate it in WordPress). | 1 |
BF_WP_INSTALL_NINJAFIREWALL | 0, 1 | Install NinjaFirewall and set up full firewall mode - only after WordPress is installed (see NinjaFirewall). | 0 |
BF_WP_REDIS_HOST | host name | Redis server for an object cache plugin. | blank |
BF_WP_REDIS_PORT | port | Redis port, if not 6379. | blank |
BF_NGINX_HEALTHCHECK_URI is set to http://localhost/wp-login.php. Nginx + PHP adds the PHP settings, and Nginx the nginx ones.
When the container starts, Nginx + PHP sets up nginx and PHP, then this image:
/wp-content is empty, fills it with WordPress's default content, then links WordPress's own wp-content folder to it/wp-content/wp-config.php from the environment variables (or keeps yours, with BF_WP_CONFIG_REGENERATE=0) and links it into WordPress. The secret keys are downloaded from WordPress.org the first time and saved in /wp-content/salt, so logins survive restarts/wp-content owned by the www user, and wp-config.php read-only (or writable, with BF_WP_CONFIG_HARDEN=0)If any step fails - e.g. a required database variable is missing - the container stops. Check docker logs.
The WordPress core files are read-only, so WordPress can't update itself - pull a new image instead. Plugins and themes are in /wp-content, so update them from WordPress as normal. The "background updates" check in Site Health is removed, as it would always fail.
With BF_WP_USE_SYSTEM_CRON=1, WordPress's own cron is turned off in wp-config.php, and the system cron runs wp-cron every minute, which requests http://localhost/wp-cron.php. Run it yourself with docker exec <container> wp-cron.
With BF_WP_INSTALL_NINJAFIREWALL=1, the plugin is installed and two files are generated so it runs in full web application firewall mode (before WordPress loads). Turn this on only after WordPress has been installed - otherwise the firewall blocks the installation. Then activate and set up the plugin in WordPress.
The site blocks hidden files (e.g. .htaccess) and PHP files in uploads (helpers/wp-restrictions.conf), and routes "pretty" permalinks to index.php. helpers/wp-subdirectory.conf is an example for running WordPress in a /wordpress sub-folder - copy it and change the name.
The WordPress version and language come from overlay/tmp/WP_VERSION and overlay/tmp/WP_LOCALE. To build a different one without changing the files, pass build arguments:
docker buildx build --build-arg WP_LOCALE=de_DE --build-arg WP_VERSION=7.1.3 -f php8.4/Dockerfile -t my-wordpress .
The version must be a released WordPress version, and the locale one that WordPress.org publishes downloads for, or the build fails. Published images always use the files.
./test.nu # builds the image and runs /test
./test.nu --all # every version, in parallel
/test runs this image's tests alongside the base images' tests, and fails if tests don't execute. They generate wp-config.php from its template and check it is valid PHP - including with quotes and backslashes in the database password - and that each optional setting appears only when enabled. They also check the build arguments, wp-config.php permissions, that existing secret keys are kept, recreating the default content, the installed WordPress version, plugin versions and PHP extensions, and that nginx accepts the configuration.
Set all four database variables: BF_WP_DB_HOST, BF_WP_DB_NAME, BF_WP_DB_USER and BF_WP_DB_PASS.
wp-config.php disappearIt is regenerated at every start. Use BF_WP_CONFIG_ADDITIONAL for extra settings, or set BF_WP_CONFIG_REGENERATE=0 to manage the file yourself.
wp-config.phpSet BF_WP_CONFIG_HARDEN=0.
http:// behind my proxyCheck the proxy sends X-Forwarded-Proto: https, and that BF_WP_ENABLE_HTTPS_BEHIND_PROXY is 1.
Core updates come with the image - pull the latest tag and recreate the container.
So nothing - a plugin, or an attacker - can change WordPress's own files. Themes, plugins and uploads are in /wp-content as normal; only the core files come from the image.
Yes - set BF_WP_REDIS_HOST (and BF_WP_REDIS_PORT if it isn't 6379) and install a Redis object cache plugin. The Redis extension is included in PHP 8 builds.
Copyright (c) 2020-2026 bfren (unless otherwise stated)
Content type
Image
Digest
sha256:cd27d454a…
Size
114.5 MB
Last updated
about 21 hours ago
docker pull bfren/wordpress:php7.4.33