Leerdoel. Aan het einde van deze tutorial heb je een compose.yaml die binnen een minuut een WordPress-installatie opstart, netjes verbindt met een MariaDB-service die écht klaar is voordat WordPress probeert te verbinden, Redis gebruikt voor de object cache, elke uitgaande e-mail in een opgevangen inbox schrijft die je in de browser kunt inspecteren, je door PHP laat steppen met Xdebug en WP-CLI beschikbaar maakt als subcommando van docker compose. Je snapt ook waarom elk onderdeel eruitziet zoals het eruitziet, zodat het volgende WordPress-containerproject dat je aanraakt niet als giswerk voelt.
Aangenomen startpunt
Je bent comfortabel op een terminal, je hebt Docker al eens voor iets anders dan een hello world gebruikt en je leest YAML zonder spiekbriefje. Je weet wat een PHP-extensie is. Je hoeft geen Compose v2 te kennen en geen eerdere Xdebug-ervaring te hebben. Kom je uit een Compose v1-achtergrond (docker-compose met het streepje), dan ziet de meeste syntax er bekend uit, maar een paar dingen zijn verschoven.
Ik heb de hele stack gebouwd tegen Docker Engine 27 en Compose v2. Compose v1 is in juni 2023 end-of-life gegaan, is in juli 2024 uit de GitHub Actions runners verwijderd en wordt niet langer ondersteund, zie Docker's eigen deprecation-aankondiging. Het commando is nu docker compose met een spatie. Typ je nog docker-compose en werkt dat, dan heb je een legacy binary op je machine staan en is het verstandig om daar van af te gaan.
Waarom Docker voor WordPress-ontwikkeling
Lokale WordPress-ontwikkeling heeft al jaren drie opties. Je installeert PHP, MySQL en een webserver direct op je laptop. Je gebruikt een one-click-app zoals LocalWP, DevKinsta of MAMP. Of je draait de hele stack in containers. De eerste optie veroudert slecht, zeker als je sites met verschillende PHP-versies onderhoudt. De tweede optie is prima tot je iets nodig hebt wat de GUI niet aanbiedt, waarna je alsnog een ondoorzichtige desktop-app zit te reverse-engineeren. De derde optie laat je de hele stack beschrijven in een tekstbestand, dat bestand in git committen, delen met de rest van het team en reproduceren op elke machine waar Docker op draait.
De praktische voordelen die er voor een WordPress-freelancer of agency toe doen: elk project krijgt een geïsoleerde PHP, MariaDB, Redis en mail-sink zonder dat je host vervuild raakt. Een compose.yaml in de repo documenteert zichzelf. Een nieuwe developer clonet, typt één commando en heeft een werkende site. Je pint PHP 8.1 voor de ene klantsite en PHP 8.3 voor de andere op dezelfde machine. En dezelfde Compose-file is prima om te verbouwen tot een CI-job die een integratieomgeving opstart voor PHPUnit- of Playwright-tests tegen een échte WordPress-installatie.
De prijs is dat je Compose goed moet leren kennen en dat er een paar WordPress-specifieke valkuilen zijn waar je makkelijk in valt. Dit artikel behandelt die direct.
Een minimale Compose v2-setup (WordPress + MariaDB)
Begin met het kleinste dat werkt: WordPress plus een database. Maak een nieuwe directory aan, voeg een bestand compose.yaml toe (de huidige conventie; docker-compose.yml werkt ook nog), en schrijf dit:
services:
db:
image: mariadb:11
restart: unless-stopped
environment:
MARIADB_DATABASE: wordpress
MARIADB_USER: wordpress
MARIADB_PASSWORD: wordpress
MARIADB_ROOT_PASSWORD: rootpassword
volumes:
- db_data:/var/lib/mysql
wordpress:
image: wordpress:php8.3-apache
restart: unless-stopped
depends_on:
- db
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
- wordpress_data:/var/www/html
volumes:
db_data:
wordpress_data:
Draai docker compose up -d, wacht een paar seconden en open http://localhost:8080. WordPress laadt de installer.
Lees het Compose-bestand even goed door. De waarde van WORDPRESS_DB_HOST is de servicenaam db, niet localhost en niet 127.0.0.1. Elke service in een Compose-bestand leeft op een privé user-defined bridge-netwerk dat Compose automatisch aanmaakt, en de DNS binnen dat netwerk vertaalt servicenamen naar container-IP's. localhost binnen de WordPress-container is de WordPress-container zelf, dus via localhost zal MariaDB nooit gevonden worden. Dit is de meest voorkomende bug wanneer iemand een losse docker run-setup naar Compose porteert, en precies dezelfde bug gaat je zo nog een keer bijten als Redis meekomt.
Het named volume wordpress_data bevat /var/www/html voor de hele WordPress-installatie. Waarom je ook bind mounts zou willen en wanneer welke keuze het beste werkt, leg ik verderop uit.
Verwachte output van docker compose ps:
NAME IMAGE STATUS
myproject-db-1 mariadb:11 Up 20 seconds
myproject-wordpress-1 wordpress:php8.3-apache Up 18 seconds
Wat deze minimale setup stiekem al fout doet, is waar de volgende sectie over gaat.
Healthchecks en de depends_on / service_healthy-gate
Herstart de stack een paar keer met docker compose down && docker compose up -d. Vroeg of laat begroet WordPress je op de eerste pageload met "Error establishing a database connection". Dat is geen netwerkbug. Het is een timingbug, en die komt omdat depends_on: - db alleen wacht tot de database-container draait, niet tot de database-service erin klaar is om queries aan te nemen.
MariaDB en MySQL hebben bij een eerste boot tien tot dertig seconden nodig om de datadirectory te initialiseren, de grant-tables aan te maken en de listener te binden. In dat window is de container "up" vanuit Docker's oogpunt, maar mysqld antwoordt nog niet. WordPress start, probeert te verbinden, faalt en toont de foutmelding. Refresh je de browser, dan werkt het meestal wel, omdat MariaDB tegen die tweede request klaar is. Dat is een beroerde dev-ervaring en in CI is het een flaky test.
De fix is een healthcheck op de db-service en WordPress laten wachten op service_healthy in plaats van het standaard service_started. Dit staat in de Compose startup order-gids.
Voor MariaDB specifiek: gebruik geen mysqladmin ping. MariaDB zelf waarschuwt in hun blogpost over MariaDB Docker healthchecks zonder mysqladmin dat mysqladmin ping success kan teruggeven tijdens de /docker-entrypoint-initdb.d init-fase, vóórdat de server daadwerkelijk queries kan verwerken. De officiële image ships een helper-script healthcheck.sh dat zowel connectie als innodb-readiness controleert. Gebruik die:
services:
db:
image: mariadb:11
restart: unless-stopped
environment:
MARIADB_DATABASE: wordpress
MARIADB_USER: wordpress
MARIADB_PASSWORD: wordpress
MARIADB_ROOT_PASSWORD: rootpassword
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
wordpress:
image: wordpress:php8.3-apache
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
- wordpress_data:/var/www/html
start_period: 30s vertelt Docker om de eerste dertig seconden falende checks te negeren, zodat de database tijdens de eerste boot niet meteen als unhealthy wordt gemarkeerd. Daarna geldt: vijf opeenvolgende failed checks markeren de container als unhealthy en Compose houdt de WordPress-container keurig in de wachtkamer. docker compose up -d blokkeert nu tot de poort daadwerkelijk open staat.
Checkpoint. Draai docker compose down -v om de volumes weg te gooien, daarna docker compose up -d. WordPress moet nu bij de allereerste request schoon booten, elke keer weer. Zie je na deze aanpassing nog Error establishing a database connection, dan is het probleem geen timing meer en moet je naar netwerknaamresolutie of credentials kijken.
Redis object cache toevoegen (service-name networking)
WordPress heeft ook op een dev-machine baat bij een object cache, omdat je dan hetzelfde codepad traint dat je productiesite gebruikt. Voeg een Redis-service toe:
redis:
image: redis:7-alpine
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
Vervolgens hang je WordPress eraan. Er is geen losse Docker environment variable voor "gebruik Redis"; je injecteert de constants in wp-config.php via WORDPRESS_CONFIG_EXTRA, dat de officiële image bij containerstart evalueert. Breid de WordPress-service uit:
wordpress:
# ...alles van hiervoor...
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DEBUG: "1"
WORDPRESS_CONFIG_EXTRA: |
define('WP_REDIS_HOST', 'redis');
define('WP_REDIS_PORT', 6379);
define('WP_CACHE', true);
De valkuil hier is exact dezelfde service-name-val die je ook bij MariaDB had, behalve dat bijna niemand 'm bij Redis lijkt te onthouden. WP_REDIS_HOST moet redis zijn, de Compose-servicenaam, niet localhost en niet 127.0.0.1. localhost binnen de WordPress-container is de WordPress-container. Redis zit in een andere container. Ik heb deze bug vaker in andermans stacks gedebugd dan ik wil toegeven, en de stille versie ("WordPress boot netjes, maar gebruikt de cache gewoon niet") is erger dan een crash.
De constants alleen zetten de cache ook niet aan. Installeer de Redis Object Cache plugin (Till Krüss) en activeer de drop-in. Zodra je de WP-CLI-service verderop in dit artikel klaar hebt staan, doe je dat in één regel:
docker compose --profile cli run --rm wpcli wp plugin install redis-cache --activate
docker compose --profile cli run --rm wpcli wp redis enable
Voor de complete flow van hoe de drop-in werkt, wat object-cache.php precies is en hoe je de hit rate verifieert, zie Redis object cache instellen in WordPress. Dat artikel dekt het verificatiestuk dat ik hier expres kort houd.
Bestandsrechten tussen host en container
WordPress draait in de officiële image als de user www-data, oftewel UID 33 op Debian. De directory wp-content/ wordt aangemaakt met rechten 1777 en www-data-ownership, zodat Apache themes, plugins en uploads kan schrijven.
Gebruik je een puur named volume (wordpress_data:/var/www/html), dan beheert Docker de filesystem en botst de UID-mapping binnen het volume met niks op je host. Dit is de makkelijke modus en het is wat het voorbeeld in deze tutorial gebruikt voor de WordPress-boom.
Zodra je een bind mount toevoegt die naar een directory in je project wijst, bijvoorbeeld ./wp-content/themes/mijntheme:/var/www/html/wp-content/themes/mijntheme, zijn de bestanden in die directory eigendom van jouw user op de host. Apache in de container wil nog steeds draaien als UID 33. Op Linux zie je doorgaans dat de themefiles eigendom zijn van een UID die binnen de container niet bestaat, en Apache kan ze dankzij de world-read bits toch lezen maar niet schrijven. Dat is voor een theme dat je in je IDE zit te bewerken meestal prima, tenzij het theme zelf in zijn eigen directory probeert te schrijven.
De WordPress-documentatie over bestandsrechten raadt 644 voor bestanden aan, 755 voor directories en 600 voor wp-config.php. Voor lokale Docker-ontwikkeling blijven die aanbevelingen staan, met één praktische toevoeging: doe nooit een chmod -R 777 binnen /var/www/html om een permissions-probleem te "fixen". 777 is een geur, geen oplossing, en als je het laat staan wijkt je dev-omgeving op een manier af van productie die er precies op de verkeerde dag toe gaat doen. Is er iets stuk, check dan eerst de ownership.
Een diepere duik in het volledige permissiemodel voor een self-managed WordPress-installatie staat in WordPress bestandsrechten.
Environment variables en wp-config.php
De officiële WordPress-image doet twee dingen met environment variables. De variabelen die met WORDPRESS_ beginnen, worden door de entrypoint van de container naar een vaste set constants in wp-config.php gemapt. WORDPRESS_DB_HOST wordt DB_HOST, WORDPRESS_TABLE_PREFIX wordt $table_prefix, WORDPRESS_DEBUG zet bij een niet-lege waarde WP_DEBUG op true. De volledige lijst, met elke ondersteunde key inclusief de _FILE-varianten voor Docker Secrets, staat in het docker-library/docs WordPress content-bestand op GitHub.
WORDPRESS_CONFIG_EXTRA is de ontsnappingsklep. Wat je daar neerzet, wordt letterlijk in wp-config.php gedropt, zo injecteer je willekeurige constants die de image zelf niet kent:
WORDPRESS_CONFIG_EXTRA: |
define('WP_REDIS_HOST', 'redis');
define('WP_REDIS_PORT', 6379);
define('WP_CACHE', true);
define('DISABLE_WP_CRON', true);
define('WP_MEMORY_LIMIT', '512M');
De YAML-pipe (|) bewaart de regeleindes, wat je wilt, zodat elke define() op zijn eigen regel in wp-config.php terechtkomt. De entrypoint van de image gebruikt PHP's eval() op deze string bij containerstart, dus syntaxfouten breken WordPress zonder enige sanity-check vooraf. Maak je een typefoutje en komt de container niet op, dan laat docker compose logs wordpress meestal de PHP parse error zien.
Eén waarschuwing: WORDPRESS_CONFIG_EXTRA draait alleen op de eerste boot, wanneer de entrypoint wp-config.php genereert vanuit wp-config-sample.php. Pas je de waarde later aan en restart je, dan gebeurt er niks tenzij je ook de bestaande wp-config.php binnen het volume weggooit. Voor een dev-stack is de simpelste reparatie docker compose down -v, dat schroot de volumes, gevolgd door up -d. Voor echte data die je niet kunt weggooien bewerk je wp-config.php direct, met WP-CLI of een texteditor.
Xdebug instellen
De officiële wordpress:php8.3-apache-image bevat geen Xdebug. Je hebt een Dockerfile van één regel nodig die de base image uitbreidt en de extensie installeert:
# Dockerfile
FROM wordpress:php8.3-apache
RUN pecl install xdebug \
&& docker-php-ext-enable xdebug
Vervang in je Compose-bestand de wordpress-service van image: naar build::
wordpress:
build: .
# ...de rest zoals eerder...
environment:
# ...bestaande WORDPRESS_*-vars...
XDEBUG_MODE: debug
XDEBUG_CONFIG: "client_host=host.docker.internal client_port=9003 start_with_request=trigger"
Twee dingen waar oudere tutorials de fout in gaan. Ten eerste heeft Xdebug 3 (sinds 2020) de oude Xdebug 2-instellingen zoals remote_host en remote_port vervangen door client_host en client_port. Elke tutorial die je vindt met remote_host is Xdebug 2 en werkt niet op een moderne container. Ten tweede is host.docker.internal de speciale hostname die Docker Desktop op macOS en Windows aanbiedt en die naar de host-machine verwijst. Op Linux is 'ie niet automatisch beschikbaar. Draai je op Linux, voeg dan een extra_hosts-regel toe, die ondersteund wordt op Docker Engine 20.10 en nieuwer:
wordpress:
# ...
extra_hosts:
- "host.docker.internal:host-gateway"
start_with_request=trigger (in plaats van yes) betekent dat Xdebug alleen activeert wanneer de request een specifieke trigger zet, doorgaans via een browser-extensie zoals Xdebug Helper. Dit is belangrijk, want start_with_request=yes activeert Xdebug bij elke request en maakt PHP drie tot vijf keer trager, wat van je dev-site een luie slideshow maakt. Trigger-modus houdt PHP snel en rekent de debugging-kosten alleen af als je daadwerkelijk wilt debuggen.
Stel je IDE in om te luisteren op poort 9003 (de Xdebug 3-default; Xdebug 2 gebruikte 9000, nog zo'n verwarringspunt) en zet de path mapping van het containerpad /var/www/html naar je projectdirectory op de host.
Checkpoint. Rebuild met docker compose build wordpress && docker compose up -d. Binnen de container moet docker compose exec wordpress php -v een regel tonen met with Xdebug v3.x.y. Zet een breakpoint in een themebestand, vuur de Xdebug-trigger af in je browser, en de IDE moet op de regel stoppen.
E-mail in development met Mailpit (niet MailHog)
WordPress verstuurt mail voor wachtwoordreset, contactformulieren, WooCommerce-orderbevestigingen en alles wat door wp_mail() gaat. In productie hang je daar een echte SMTP-dienst aan. In lokale ontwikkeling wil je geen echte mail je machine zien verlaten, en je wilt ook niet hoeven gokken of die mail überhaupt wel verstuurd is. De oplossing is een lokale SMTP-catch-all die alles opvangt en je een webinterface geeft om het te inspecteren.
De meeste tutorials over dit onderwerp raden nog steeds MailHog aan. MailHog is sinds 2020 niet meer geüpdatet en is in de praktijk verlaten. De actief onderhouden drop-in-vervanger is Mailpit van Ralph Slooten. Mailpit gebruikt dezelfde default-poorten als MailHog (1025 voor SMTP, 8025 voor de UI), heeft een kleinere image, biedt full-text search en ondersteunt TLS. Voor een verse stack in 2026 is er geen reden om nog naar MailHog te grijpen.
Voeg de service toe:
mailpit:
image: axllent/mailpit:latest
restart: unless-stopped
ports:
- "8025:8025"
environment:
MP_SMTP_AUTH_ALLOW_INSECURE: "true"
MP_MAX_MESSAGES: "500"
Alleen poort 8025 wordt naar de host gepubliceerd, want dat is de web UI. SMTP op 1025 blijft in het Compose-netwerk, waar WordPress het als mailpit:1025 kan bereiken.
WordPress aan Mailpit hangen is waar het even ongemakkelijk wordt. De officiële WordPress-image heeft geen native SMTP-configuratie, dus wp_mail() valt terug op PHP's mail()-functie, en die gaat in een container zonder MTA nergens heen. Je hebt twee praktische opties. Installeer een plugin zoals WP Mail SMTP en wijs die naar host mailpit, poort 1025, geen encryptie, geen authenticatie. Of voeg een piepklein must-use plugin toe dat aan de phpmailer_init-action hangt en de transport omzet. De plugin-route is meestal de minste friction en is vanuit de admin te configureren.
Heb je liever het codepad, maak dan wp-content/mu-plugins/mailpit.php op je host aan (bind-mount die in de container) met:
<?php
// Route wp_mail via Mailpit in Docker.
add_action('phpmailer_init', function ($mailer) {
$mailer->isSMTP();
$mailer->Host = 'mailpit';
$mailer->Port = 1025;
$mailer->SMTPAuth = false;
$mailer->SMTPSecure = '';
});
Trigger een willekeurige WordPress-mailflow (vraag bijvoorbeeld een wachtwoordreset aan) en open dan http://localhost:8025 in je browser. Het bericht staat in de Mailpit-inbox met volledige headers, HTML-body en eventuele attachments.
Voor het bredere plaatje van hoe WordPress mail aflevert en waarom lokale SMTP-opvang bij een gezonde dev-workflow hoort, zie waarom WordPress geen e-mail verstuurt.
WP-CLI in een Docker-container
De standaard wordpress:php8.3-apache-image bevat geen WP-CLI. WP-CLI heeft zijn eigen officiële image, wordpress:cli, die op Alpine is gebaseerd en alleen de CLI zelf bevat, niet WordPress. Je gebruikt 'm door een tweede service toe te voegen die dezelfde volumes en environment variables deelt als de WordPress-service.
En hier zit de meest obscure valkuil van de hele tutorial. Op Debian (wat de wordpress:php8.3-apache-image gebruikt) is de user www-data UID 33. Op Alpine (wat de wordpress:cli-image gebruikt) is www-data UID 82. Draai je wordpress:cli zonder de user te overschrijven, dan raakt 'ie bestanden aan als UID 82, en die bestanden zijn opeens niet meer toegankelijk voor de UID 33-Apache. De symptomen variëren van stille permissions-fouten tot plugins die geen bestanden kunnen schrijven tot uploads die niet meer werken. De fix is WP-CLI expliciet als UID 33 te laten draaien:
wpcli:
image: wordpress:cli
depends_on:
wordpress:
condition: service_started
user: "33:33"
volumes:
- wordpress_data:/var/www/html
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
profiles:
- cli
De regel profiles: [cli] houdt wpcli uit de standaard docker compose up-set. Je wilt 'm alleen on-demand draaien, niet als long-running service. Aanroepen gaat zo:
docker compose --profile cli run --rm wpcli wp core version
docker compose --profile cli run --rm wpcli wp plugin list
docker compose --profile cli run --rm wpcli wp user create dev dev@localhost --role=administrator --user_pass=devdev
Verwachte output van het eerste commando:
6.7.1
Vergeet je user: "33:33", dan krijg je niet meteen een fout, maar de eerste keer dat WP-CLI een bestand aanmaakt (plugin installeren, wp config create, upload-rewrite) begint je WordPress-container zich raar te gedragen. Ik heb hele avonden aan deze val verloren. Zet de user en ga door.
Verhuis je ditzelfde stack-patroon later van lokale dev naar een self-managed server, dan is de full-page cache vóór PHP-FPM de volgende hefboom. Zie WordPress + Nginx FastCGI cache voor de productie-tegenhanger van dit artikel.
macOS volume-performance (named volumes vs bind mounts)
Op Linux draait Docker op de host-kernel en halen bind mounts bijna native performance. Op macOS draait Docker Desktop binnen een virtual machine via het Apple Virtualization Framework, en elke filesystem-toegang via een bind mount kruist de VM-grens. Met VirtioFS, wat sinds Docker Desktop 4.x de default op macOS is, zijn bind mounts voor een doorsnee WordPress-workload ongeveer drie keer trager dan named volumes, aldus de Docker Desktop performance guide voor Mac en de 2025-editie van de Docker storage volume-vergelijking. Vóór VirtioFS was dat gat vijf tot zes keer. Het is nu beter. Het is nog steeds niet gratis.
Voor een WordPress dev-stack op een Mac is het patroon dat goed presteert zonder in te leveren op editability een named volume voor de volledige WordPress-installatie gecombineerd met selectieve bind mounts voor alleen de directories waar je actief aan werkt:
wordpress:
build: .
# ...
volumes:
- wordpress_data:/var/www/html
- ./wp-content/themes/mijntheme:/var/www/html/wp-content/themes/mijntheme
- ./wp-content/plugins/mijnplugin:/var/www/html/wp-content/plugins/mijnplugin
Dit geeft je snelle database-adjacent toegang voor de duizenden WordPress core- en plugin-bestanden die je nooit aanraakt, en host-filesystemsnelheid voor de ene theme en ene plugin waar je aan werkt. De hele wp-content/-directory bind-mounten op macOS is verleidelijk en meteen de snelste manier om een WooCommerce dev-site te laten voelen als een netbook uit 2010. Doe het niet. De zware plugin-bomen (WooCommerce, Elementor, page builders) genereren genoeg filesystem-verkeer om VirtioFS te verzuipen.
Wil je het hele Docker Desktop-performance-verhaal op macOS omzeilen, dan staan OrbStack en Lima in 2026 allebei goed aangeschreven en gaan ze merkbaar beter om met bind mounts dan Docker Desktop. Allebei een avondje evaluatie waard als bind mount-performance je bottleneck is.
Op Linux geldt dit allemaal niet. Bind mounts zijn prima. Sla deze sectie over.
Docker vs LocalWP vs DevKinsta voor lokale ontwikkeling
LocalWP (van WP Engine) en DevKinsta (van Kinsta) zijn desktop-apps die je met één klik een WordPress-omgeving geven zonder ooit een terminal open te hoeven. Ze hebben hun plek. Ze zijn ook niet hetzelfde gereedschap als Docker Compose, en het is eerlijk om te vertellen waar elke optie wint.
| Criterium | Docker Compose | LocalWP | DevKinsta |
|---|---|---|---|
| Reproduceerbaarheid tussen machines | Excellent (compose.yaml in git) |
Zwak (lokale DB-dumps) | Zwak |
| PHP-versie per project | Ja, elke versie | Beperkte set | Beperkte set |
| Werkt in CI | Ja, zelfde bestand | Nee | Nee |
| Team delen | Git commit | Export/import site | Export/import site |
| Leercurve | Gemiddeld | Laag | Laag |
| Aanpasbaarheid | Alles wat in een container draait | Wat de GUI toont | Wat de GUI toont |
| WP-CLI-toegang | Ja, na inregelen | Ja, ingebouwd | Ja, ingebouwd |
| macOS-performance | Tunen met named volumes | Snel | Snel |
| Parity met managed hosting | Bouw je zelf | Matcht WP Engine | Matcht Kinsta |
Kies Docker Compose als je PHP-versiepariteit met een productieserver nodig hebt, als meer dan één developer aan de codebase werkt, als het project door gaat groeien naar CI-integration-tests, als je voor andere delen van je stack al met containers werkt, of als je de setup in git wilt hebben. Dit is de default voor agencies en developers.
Kies LocalWP of DevKinsta als je alleen aan de site werkt, als je nul setup wilt, als je geen specifieke serveromgeving hoeft te matchen, of als je een designer bent die een WordPress-speeltuin wil in plaats van een dev-omgeving. Beide zijn uitstekend in wat ze doen. Geen van beide vervangt Compose voor een team dat code naar een niet-triviale productiestack shipt.
Het eerlijke nadeel van Docker Compose voor WordPress is dat de eerste setup een paar uur lees- en debug-werk kost. Dit artikel is het grootste deel van dat leeswerk. Het eerlijke nadeel van LocalWP en DevKinsta is dat zodra je iets nodig hebt wat de GUI niet aanbiedt, het enige antwoord is je site te exporteren en ergens anders opnieuw op te bouwen.
Wat je hebt geleerd
- Een Compose v2-bestand beschrijft een volledige WordPress dev-stack in version control.
docker-compose(v1) is weg; het huidige commando isdocker compose. depends_onzondercondition: service_healthyis een geladen geweer voor WordPress + MariaDB-boots. Altijd een echte healthcheck declareren en daarop gaten.- Redis in Compose bereik je als
redis, niet alslocalhost. Dezelfde regel geldt voor elke service in je netwerk. WORDPRESS_CONFIG_EXTRAis hoe je willekeurigedefine()-calls inwp-config.phpinjecteert, en dat draait alleen bij de eerste boot.- Xdebug 3 is de huidige generatie, leeft in een eigen Dockerfile, en je wilt
start_with_request=triggertenzij je geniet van je dev-site op kwartsnelheid. - Mailpit vervangt het verlaten MailHog als lokale SMTP-catch-all, met dezelfde poorten.
- WP-CLI leeft in een losse
wordpress:cli-image, heeftuser: "33:33"nodig om de Debian-UID te matchen, en hoort achter een Compose-profile zodat 'ie niet standaard mee opstart. - Op macOS is een named volume voor de WordPress-boom plus selectieve bind mounts voor de directories waar je aan werkt, de balans tussen snelheid en developer-ergonomie. Op Linux speelt dit niet.
Complete eind-compose.yaml
services:
db:
image: mariadb:11
restart: unless-stopped
environment:
MARIADB_DATABASE: wordpress
MARIADB_USER: wordpress
MARIADB_PASSWORD: wordpress
MARIADB_ROOT_PASSWORD: rootpassword
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
redis:
image: redis:7-alpine
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
mailpit:
image: axllent/mailpit:latest
restart: unless-stopped
ports:
- "8025:8025"
environment:
MP_SMTP_AUTH_ALLOW_INSECURE: "true"
MP_MAX_MESSAGES: "500"
wordpress:
build: .
restart: unless-stopped
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DEBUG: "1"
WORDPRESS_CONFIG_EXTRA: |
define('WP_REDIS_HOST', 'redis');
define('WP_REDIS_PORT', 6379);
define('WP_CACHE', true);
XDEBUG_MODE: debug
XDEBUG_CONFIG: "client_host=host.docker.internal client_port=9003 start_with_request=trigger"
# Uncomment op Linux als host.docker.internal niet resolvet:
# extra_hosts:
# - "host.docker.internal:host-gateway"
volumes:
- wordpress_data:/var/www/html
- ./wp-content/themes/mijntheme:/var/www/html/wp-content/themes/mijntheme
- ./wp-content/plugins/mijnplugin:/var/www/html/wp-content/plugins/mijnplugin
wpcli:
image: wordpress:cli
depends_on:
wordpress:
condition: service_started
user: "33:33"
volumes:
- wordpress_data:/var/www/html
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
profiles:
- cli
volumes:
db_data:
wordpress_data:
En de bijbehorende Dockerfile in dezelfde directory:
FROM wordpress:php8.3-apache
RUN pecl install xdebug \
&& docker-php-ext-enable xdebug
Breng hem op met docker compose up -d, shell erin met docker compose exec wordpress bash, en draai WP-CLI met docker compose --profile cli run --rm wpcli wp <commando>. Vanaf hier is de stack van jou. Wanneer je klaar bent om verder te gaan dan single-host Docker Compose en naar productie-Kubernetes te verhuizen, zie Migreren van Docker Compose naar Kubernetes.