Sign inSign up

bfren/wordpress

By bfren

•Updated about 21 hours ago

WordPress installed on Alpine Linux, with auto-generated configuration.

Image
0

100K+

bfren/wordpress repository overview

⁠WordPress

GitHub release (latest by date) Docker Pulls Docker Image Size GitHub Workflow Status

Docker Hub⁠ - Quay.io⁠ - bfren ecosystem⁠

⁠Introduction

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:

  • WordPress 7.1.3 (British English) installed read-only, so core updates come from pulling a new image and can't be changed by plugins or attackers
  • the PHP extensions WordPress requires and recommends, including Imagick and Redis (PHP 8 builds)
  • wp-config.php generated from environment variables, with secret keys downloaded once and kept
  • WordPress's scheduled tasks run every minute by the system's cron, instead of on page loads
  • the WP-WebAuthn⁠ plugin (passkey logins) and, optionally, NinjaFirewall⁠ installed for you
  • nginx rules that block access to hidden files and to PHP files in uploads

It is designed to run behind a reverse proxy that handles SSL, such as Caddy Proxy⁠.

⁠Quickstart

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).

⁠Features

  • 🔒 Read-only WordPress core, with minor updates only through new images
  • 📝 wp-config.php generated from environment variables at every start
  • ⏰ System cron for WordPress's scheduled tasks
  • 🛡️ Optional NinjaFirewall in full web application firewall mode, and WebAuthn passkey logins
  • ⚡ Redis object cache settings, for use with a Redis plugin
  • 🐘 PHP 7.4 to 8.5 - choose with the image tag
  • 🗜️ Everything from Nginx + PHP⁠: PHP-FPM, OPcache, compression and logs
  • 🩺 Health check that loads the login page

⁠Tags

Images 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.

TagExampleUse when
phpX.Yphp8.4You want the latest image for a PHP version
phpX.Y-MAJORphp8.4-7You want updates, but not breaking changes
phpX.Y-MINORphp8.4-7.2You want fixes only - recommended
phpX.Y-VERSIONphp8.4-7.2.0You want to pin this image exactly
phpX.Y-wpA.Bphp8.4-wp7.1You want to stay on a WordPress version
phpX.Y-wpA.B.Cphp8.4-wp7.1.3You want an exact WordPress release
…-VERSION versions of bothphp8.4-wp7.1.3-7.2.0You want to pin both
phpX.Y.Zphp8.4.26You 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.

⁠Configuration

⁠Ports
PortPurpose
80HTTP - put a reverse proxy in front of it for SSL.
⁠Volumes
VolumePurpose
/wp-contentThemes, plugins, uploads and languages, plus wp-config.php and salt (the secret keys). Back it up.
⁠Environment Variables
VariableValuesDescriptionDefault
BF_WP_DB_HOSThost nameRequired. Database server.blank
BF_WP_DB_NAMEtextRequired. Database name.blank
BF_WP_DB_USERtextRequired. Database user.blank
BF_WP_DB_PASStextRequired. Database password.blank
BF_WP_DB_PREFIXtextTable prefix - an underscore is added (e.g. wp_).wp
BF_WP_DB_CHARSETtextDatabase character set.utf8mb4
BF_WP_DB_COLLATEtextDatabase collation - leave blank unless you know you need it.blank
BF_WP_DEBUGtrue, falseWordPress debug mode.false
BF_WP_CONFIG_REGENERATE0, 11 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_HARDEN0, 11 makes wp-config.php read-only to WordPress. 0 makes it writable, for plugins that need to change it.1
BF_WP_CONFIG_ADDITIONALPHP codeExtra lines added to wp-config.php, e.g. define( 'WP_MEMORY_LIMIT', '256M' );.blank
BF_WP_ENABLE_HTTPS_BEHIND_PROXY0, 1Treat requests as HTTPS when the proxy sends X-Forwarded-Proto: https.1
BF_WP_USE_SYSTEM_CRON0, 1Run scheduled tasks from the system's cron every minute, instead of during page loads.1
BF_WP_DISABLE_MAJOR_UPDATES0, 1Limit WordPress's automatic core updates to minor releases.1
BF_WP_INSTALL_WEBAUTHN0, 1Install the WP-WebAuthn plugin (you still activate it in WordPress).1
BF_WP_INSTALL_NINJAFIREWALL0, 1Install NinjaFirewall and set up full firewall mode - only after WordPress is installed (see NinjaFirewall⁠).0
BF_WP_REDIS_HOSThost nameRedis server for an object cache plugin.blank
BF_WP_REDIS_PORTportRedis 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.

⁠How It Works

⁠Starting

When the container starts, Nginx + PHP⁠ sets up nginx and PHP, then this image:

  1. Content - if /wp-content is empty, fills it with WordPress's default content, then links WordPress's own wp-content folder to it
  2. Configuration - generates /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
  3. Plugins - installs WP-WebAuthn and NinjaFirewall, if enabled and not already installed
  4. Permissions - makes /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.

⁠Updates

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.

⁠Scheduled tasks

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.

⁠NinjaFirewall

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.

⁠nginx

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.

⁠Building another version or language

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.

⁠Testing

./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.

⁠Troubleshooting

⁠The container stops at start-up with "You must define WP_DB_…"

Set all four database variables: BF_WP_DB_HOST, BF_WP_DB_NAME, BF_WP_DB_USER and BF_WP_DB_PASS.

⁠Changes I made to wp-config.php disappear

It 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.

⁠A plugin says it can't write to wp-config.php

Set BF_WP_CONFIG_HARDEN=0.

Check the proxy sends X-Forwarded-Proto: https, and that BF_WP_ENABLE_HTTPS_BEHIND_PROXY is 1.

⁠WordPress says an update is available but won't install it

Core updates come with the image - pull the latest tag and recreate the container.

⁠FAQs

⁠Why is WordPress read-only?

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.

⁠Can I use Redis for caching?

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.

⁠Licence

Copyright (c) 2020-2026 bfren⁠ (unless otherwise stated)

Tag summary

Content type

Image

Digest

sha256:cd27d454a…

Size

114.5 MB

Last updated

about 21 hours ago

docker pull bfren/wordpress:php7.4.33