Wp docker compose stack
Skill Lonsdale201/wp-agent-skills/dev-tooling/wp-docker-compose-stack
A community-maintained collection of agent skills for WordPress plugin and theme development.
npx -y skills add Lonsdale201/wp-agent-skills --skill wp-docker-compose-stackAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 21 stars21 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Build a custom docker-compose development stack for WordPress when wp-env's abstraction runs out — Redis object cache, Mailpit SMTP capture, custom PHP extensions (phpredis, Xdebug), php.ini overrides. Covers the official wordpress image's runtime-env config model (wp-config.php reads WORDPRESS_DB_* / WORDPRESS_CONFIG_EXTRA via getenv at request time, so every container sharing the volume — wp-cli included — needs the same environment, best shared via a YAML anchor), the mariadb healthcheck + service_healthy dependency, a wp-cli service as user 33:33, wiring the redis-cache plugin drop-in with WP_REDIS_HOST, why wp_mail silently fails (invalid wordpress@localhost From) and the phpmailer_init + Mailpit fix, pecl Dockerfiles for phpredis/Xdebug, and pinning image tags because wordpress:latest lags WP releases. Use when composing docker-compose.yml for WP, adding Redis/mail capture to local dev, or debugging a WP container where config env vars seem ignored.
The file declares its own license as GPLv2-or-later. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
11.6 KB, ~2.6k tokens by cl100k_base, as published. Nobody here has run it
A custom docker-compose stack for WordPress
Hand-write a compose stack when you need services the official tooling doesn't model: Redis object cache, SMTP capture, custom PHP extensions, production-like topology. Default to wp-env first (see wp-env-local-dev) — it covers plain plugin/block development with less to maintain. This skill is the escape hatch, grounded against a fully booted and verified stack (WordPress + MariaDB + Redis + Mailpit + wp-cli, drop-in connected, mail captured). The complete tested files are bundled under examples/.
When to use this skill
- Writing or reviewing a
docker-compose.ymlfor WordPress development. - Adding Redis (object cache), Mailpit (mail capture), Xdebug, or php.ini overrides to a WP container setup.
- Debugging "my
WORDPRESS_CONFIG_EXTRA/WP_REDIS_HOSTis ignored" or "wp-cli sees different config than the site". wp_mailsilently fails inside a container.- A team needs a production-like local topology that
wp-envcan't express.
The config model: env is read at request time
This is the load-bearing fact of the official wordpress image, and the source of most broken stacks. The generated wp-config.php does not bake values in — it calls getenv_docker() on every request, and WORDPRESS_CONFIG_EXTRA is eval'd from the environment at runtime.
Consequence (verified by hitting it): a wp-cli container sharing the same volume gets none of that config unless it has the same environment variables. Our wp redis enable failed with Connection refused [tcp://127.0.0.1:6379] because only the wordpress service had WORDPRESS_CONFIG_EXTRA with WP_REDIS_HOST — the wp-cli service silently fell back to defaults. Share the block with a YAML anchor:
x-wp-environment: &wp-environment
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
WORDPRESS_CONFIG_EXTRA: |
define( 'WP_REDIS_HOST', 'redis' );
if ( ! defined( 'WP_DEBUG' ) ) { define( 'WP_DEBUG', true ); }
define( 'WP_DEBUG_LOG', true );
services:
wordpress:
environment: *wp-environment
wpcli:
environment: *wp-environment
Two refinements, both verified:
- Guard
WP_DEBUGwithif ( ! defined(...) )— wp-cli defines it first and an unguarded define inCONFIG_EXTRAthrows "Constant WP_DEBUG already defined" warnings on every cli call. (Alternative: the image's ownWORDPRESS_DEBUG: 1env var togglesWP_DEBUGwithout touchingCONFIG_EXTRA.) - Other image env vars:
WORDPRESS_TABLE_PREFIX, theWORDPRESS_AUTH_KEY…WORDPRESS_NONCE_SALTsalts (auto-generated if omitted), and a_FILEsuffix convention on all of them for Docker secrets.
Baseline stack
Abbreviated (full tested file: examples/docker-compose.yml):
services:
db:
image: mariadb:lts
environment:
MARIADB_DATABASE: wordpress
MARIADB_USER: wordpress
MARIADB_PASSWORD: wordpress
MARIADB_ROOT_PASSWORD: root
volumes: [ db_data:/var/lib/mysql ]
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
timeout: 5s
retries: 12
wordpress:
image: wordpress:latest # pin in real projects — see below
depends_on:
db:
condition: service_healthy
ports: [ "8080:80" ]
environment: *wp-environment
volumes:
- wp_data:/var/www/html
- ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
- The healthcheck is not optional. WordPress crashes into a restart loop if it races a still-initializing MariaDB;
condition: service_healthywith mariadb's bundledhealthcheck.sh(verified) removes the race. - Pin the image.
wordpress:latestlags WordPress releases — at test time it shipped WP 6.8.3 / PHP 8.3 while the current release was 7.0.1 (whichwp-envdid install). Use an explicit tag likewordpress:6.8-php8.3-apache, and update WP itself viawp core updatein the volume when needed. - php.ini overrides are a one-line mount into
/usr/local/etc/php/conf.d/— verifiedupload_max_filesize/memory_limittook effect. Seeexamples/uploads.ini. - For plugin development, add a bind mount:
./:/var/www/html/wp-content/plugins/my-plugin(on top of the named volume).
wp-cli as a service
wpcli:
image: wordpress:cli
user: "33:33"
profiles: ["cli"] # doesn't start with `up`, only via `run`
depends_on:
db: { condition: service_healthy }
environment: *wp-environment
volumes: [ wp_data:/var/www/html ]
docker compose run --rm wpcli wp core install \
--url=http://localhost:8080 --title="Dev" \
--admin_user=admin --admin_password=admin \
[email protected] --skip-email
user: "33:33"matters: the Alpine-based cli image defaults to uid 82, the Debian Apache image serves as uid 33 (www-data). Verified: with 33:33, files wp-cli writes (plugin installs, the Redis drop-in) come out owned bywww-dataand stay editable by the web container.- The
Failed to create directory '/.wp-cli/cache/'warning is benign (uid 33 has no writable HOME); silence it withHOME: /tmpin the environment if it bothers you. - Wait for first boot before installing: the
wordpresscontainer generateswp-config.phpon first run — until then wp-cli has nothing to load.
Redis object cache (verified end-to-end)
redis:
image: redis:alpine
With WP_REDIS_HOST pointing at the service name (in the shared anchor above):
docker compose run --rm wpcli wp plugin install redis-cache --activate
docker compose run --rm wpcli wp redis enable
docker compose run --rm wpcli wp redis status # → Status: Connected, Drop-in: Valid, Ping: PONG
The official image ships no phpredis extension — verified: the drop-in falls back to the plugin's bundled Predis client, which works fine for dev. For phpredis (production parity), a two-line Dockerfile (verified build): FROM wordpress:latest + RUN pecl install redis && docker-php-ext-enable redis. For what the object cache changes about wp_cache_* semantics, see wp-redis-object-cache.
Mail capture with Mailpit (and why wp_mail fails first)
mailpit:
image: axllent/mailpit
ports: [ "8025:8025" ] # web UI + API; SMTP is 1025 inside the network
Two things must both be fixed — verified by hitting each failure separately:
- WordPress doesn't speak SMTP by default (and the container has no sendmail): route PHPMailer to Mailpit.
- The default From address is invalid in Docker. With a
localhostsite URL, WP buildsFrom: wordpress@localhost, which PHPMailer rejects (Invalid address) — sowp_mail()returnsfalsebefore any SMTP attempt, with no output unless you listen towp_mail_failed.
Drop this as an mu-plugin (wp-content/mu-plugins/mailpit.php):
<?php
add_action( 'phpmailer_init', function ( $phpmailer ) {
$phpmailer->isSMTP();
$phpmailer->Host = 'mailpit';
$phpmailer->Port = 1025;
} );
add_filter( 'wp_mail_from', fn() => '[email protected]' );
Verified: with both in place the message lands in Mailpit (UI at http://localhost:8025, scriptable via GET /api/v1/messages). When debugging, hook wp_mail_failed and print the WP_Error — it names the real cause.
Xdebug
The official image has no Xdebug; add it with a Dockerfile (verified build, Xdebug 3.5.3 — full file: examples/Dockerfile.xdebug):
FROM wordpress:latest
RUN pecl install xdebug && docker-php-ext-enable xdebug
Configure via a conf.d ini: xdebug.mode=debug, xdebug.start_with_request=trigger, xdebug.client_host=host.docker.internal, xdebug.client_port=9003. host.docker.internal reaches the IDE on Docker Desktop; on native Linux add it via extra_hosts: ["host.docker.internal:host-gateway"].
Critical rules
- Every container that loads WordPress needs the same
WORDPRESS_*environment — the image resolves config from env at request time, not at install time. Use a YAML anchor; a config that "works on the site but not in wp-cli" is this bug. - Healthcheck the DB and gate WordPress on
service_healthy—depends_onalone only orders startup, it doesn't wait for MySQL to accept connections. - Pin image tags.
wordpress:latestlags WP releases and silently changes PHP major versions under you. - Run wp-cli as
user: "33:33"so written files stay owned bywww-data; alpine-cli's default uid 82 leaves root-unfriendly ownership mismatches. wp_mailin a container needs a valid From address AND an SMTP route — fix both (wp_mail_from+phpmailer_init), and debug viawp_mail_failed, never by staring at a silentfalse.- Guard constants in
CONFIG_EXTRA(if ( ! defined(...) )) — wp-cli pre-defines some (WP_DEBUG) and unguarded defines warn on every invocation. - Named volumes for
/var/www/htmland the DB —docker compose downkeeps them,down -vis the deliberate wipe.
Cross-references
- Run
wp-env-local-devfirst — if the project needs no custom services, the official tool is less to maintain. - Run
wp-redis-object-cachefor correctwp_cache_*usage and drop-in lifecycle once Redis is wired. - Run
wp-phpunit-test-setupfor the test harness; its wp-env route already includes the WP test suite, no extra compose service needed.
What this skill does NOT cover
- Production hosting, TLS, scaling, or hardening — this is a development stack.
wp-envitself (seewp-env-local-dev) and WordPress Playground.- Node build tooling (Tailwind, @wordpress/scripts): run watchers on the host — file-watching through container mounts is slow and gains nothing locally; containerize builds only in CI.
References
- Official wordpress image docs (env vars, variants,
_FILEsecrets): https://hub.docker.com/_/wordpress - mariadb image healthcheck.sh: https://mariadb.com/kb/en/using-healthcheck-sh/
- Mailpit: https://mailpit.axllent.org/
- Redis Object Cache plugin: https://wordpress.org/plugins/redis-cache/
- Bundled, boot-verified files:
examples/docker-compose.yml,examples/uploads.ini,examples/Dockerfile.xdebug,examples/mu-plugin-mailpit.php