WordPress mit Docker Compose betreiben: produktionsnah und sicher einrichten
WordPress mit Docker Compose für den Dauerbetrieb einrichten: feste Image-Versionen, Passwörter als Secrets, Port nur lokal mit Reverse Proxy, PHP-Grenzen, E-Mail-Versand, WP-CLI, Updates auf zwei Ebenen und Backups mit mariadb-dump.
Mit KI erstellt – redaktionelle Prüfung ausstehend
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Mehr dazu

Docker Compose ist ein bequemer Weg, WordPress auf einem eigenen Server zu betreiben: Webserver, PHP und Datenbank laufen in getrennten Containern, die ganze Konfiguration steht in einer Datei, und ein Umzug auf einen anderen Server ist schnell erledigt. Die Beispiele aus dem Netz sind aber fast immer für den Schnellstart gedacht. Sie enthalten Passwörter im Klartext, öffnen Port 80 für die ganze Welt und verschweigen, dass das offizielle Image keine E-Mails versenden kann. Diese Anleitung baut eine Compose-Datei, die für den Dauerbetrieb taugt, und erklärt die Stellen, an denen sich das offizielle WordPress-Image anders verhält, als viele erwarten. Die Einzelteile wurden im Labor mit dem offiziellen Image und MariaDB 11.8 ausprobiert.
Voraussetzungen
- Server: ein Linux-Server mit Docker Engine und dem Compose-Plugin. Für eine Unternehmenswebsite mit normalem Besucheraufkommen genügen 2 CPU-Kerne und 4 GB RAM, x86 oder ARM.
- WordPress: das offizielle Image, hier in Version 7.1.2 mit PHP 8.4 und Apache. PHP 7.4 ist laut WordPress-API das empfohlene Minimum, das Image bringt PHP 8.4 selbst mit.
- Datenbank: MariaDB 11.8, die aktuelle Langzeitversion.
- Zugriff: SSH mit sudo-Rechten auf dem Server. Nach der Einrichtung ein WordPress-Konto mit der Rolle Administrator.
- Domain und HTTPS: eine Domain, die auf den Server zeigt, und ein Reverse Proxy für die Zertifikate, zum Beispiel Nginx. Wie das geht, zeigt Nginx als Reverse Proxy mit TLS.
- Grundschutz: Firewall und SSH-Schlüssel sind eingerichtet, siehe VPS absichern und härten.
- Backup: Ziehen Sie eine bestehende Website um, brauchen Sie ein aktuelles Backup von Dateien und Datenbank der alten Installation.
Schritt 1: Projektordner und Secrets anlegen
Legen Sie für das Projekt einen eigenen Ordner an. Passwörter kommen nicht in die Compose-Datei, sondern in einzelne Dateien im Unterordner secrets. So landen sie weder in der Versionsverwaltung noch in der Ausgabe von docker inspect als Umgebungsvariable.
sudo mkdir -p /opt/wordpress/secrets
cd /opt/wordpress
openssl rand -base64 32 | sudo tee secrets/db_password > /dev/null
openssl rand -base64 32 | sudo tee secrets/db_root_password > /dev/null
sudo chmod 700 secrets
Sperren Sie den Ordner mit chmod 700, lassen Sie die Dateien darin aber lesbar. Die Prozesse in den Containern laufen nicht als root und müssen die Dateien lesen können. Sind die Dateien selbst auf 600 gesetzt, kann der Container sie nicht lesen, und die Datenbankverbindung scheitert.
Sowohl das WordPress- als auch das MariaDB-Image lesen Werte aus Dateien, wenn Sie an den Variablennamen _FILE anhängen. Das ist in der Image-Beschreibung beider Projekte dokumentiert. Im Labor wurde das für WordPress nachgeprüft: Mit WORDPRESS_DB_PASSWORD_FILE stand in der Konstante DB_PASSWORD genau der Inhalt der Datei.
Verifizieren: sudo ls -l /opt/wordpress/secrets zeigt zwei Dateien, sudo wc -c secrets/* jeweils etwa 45 Zeichen.
Schritt 2: Die Compose-Datei schreiben
Legen Sie /opt/wordpress/compose.yaml mit diesem Inhalt an:
services:
db:
image: mariadb:11.8
restart: unless-stopped
environment:
MARIADB_DATABASE: wordpress
MARIADB_USER: wordpress
MARIADB_PASSWORD_FILE: /run/secrets/db_password
MARIADB_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
secrets: [db_password, db_root_password]
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
wordpress:
image: wordpress:7.1.2-php8.4-apache
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
WORDPRESS_CONFIG_EXTRA: |
define( 'DISALLOW_FILE_EDIT', true );
secrets: [db_password]
ports:
- "127.0.0.1:8080:80"
volumes:
- wp_data:/var/www/html
- ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini:ro
secrets:
db_password:
file: ./secrets/db_password
db_root_password:
file: ./secrets/db_root_password
volumes:
db_data:
wp_data:
Die wichtigsten Entscheidungen im Überblick:
| Einstellung | Warum |
|---|---|
| feste Image-Tags | ein docker compose pull bringt keine ungeplante neue Hauptversion |
127.0.0.1:8080:80 | WordPress ist nur für den Reverse Proxy auf demselben Server erreichbar |
Datenbank ohne ports | MariaDB ist nur im internen Compose-Netz erreichbar |
healthcheck.sh | WordPress startet erst, wenn die Datenbank wirklich antwortet |
DISALLOW_FILE_EDIT | schaltet den Datei-Editor im Backend ab |
Das Skript healthcheck.sh ist Teil des offiziellen MariaDB-Images. Ein Healthcheck mit curl oder wget würde dort scheitern, weil diese Programme im Image fehlen. Im Labor meldete healthcheck.sh --connect --innodb_initialized bei laufender Datenbank den Exit-Code 0. Über WORDPRESS_CONFIG_EXTRA fügen Sie beliebige PHP-Zeilen in die wp-config.php ein. Das Image führt den Inhalt beim Laden der Konfiguration aus, im Test war die Konstante danach gesetzt.
Verifizieren: docker compose config gibt die Datei ohne Fehlermeldung aus. Die Passwörter selbst erscheinen dort nicht, nur die Pfade zu den Secret-Dateien.
Schritt 3: PHP-Grenzen anpassen und starten
Das Image setzt die PHP-Standardwerte. Im Labor lag die Obergrenze für Uploads bei 2 MB und für Formulardaten bei 8 MB. Für Fotos oder PDFs reicht das oft nicht. Legen Sie deshalb neben der Compose-Datei uploads.ini an:
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
Die Image-Beschreibung empfiehlt, eigene Werte als Datei in $PHP_INI_DIR/conf.d/ abzulegen. Genau dorthin bindet die Compose-Datei die Datei ein. Im Test war der neue Upload-Wert nach dem Neustart des Webservers aktiv. Starten Sie jetzt den Stack:
sudo docker compose up -d
sudo docker compose ps
curl -I http://127.0.0.1:8080/
Beim ersten Start kopiert das Image WordPress in das Volume und schreibt eine wp-config.php mit zufälligen Sicherheitsschlüsseln. Danach richten Sie über den Reverse Proxy und Ihre Domain die Installation im Browser ein.
Verifizieren: docker compose ps zeigt db als „healthy“ und wordpress als „running“. curl -I liefert eine Antwort vom Apache im Container, bei einer frischen Installation eine Weiterleitung zur Einrichtung. Unter Werkzeuge > Website-Zustand, Reiter „Information“, Abschnitt „Server“, steht nach der Installation die neue Upload-Grenze.
Schritt 4: Reverse Proxy und HTTPS richtig anbinden
WordPress sieht hinter dem Proxy nur eine unverschlüsselte Verbindung. Damit es trotzdem HTTPS-Adressen erzeugt, muss der Proxy den Header X-Forwarded-Proto: https mitschicken. Die wp-config.php des Images wertet ihn bereits aus und setzt dann intern HTTPS. Im Labor erzeugte WordPress mit diesem Header durchgehend https://-Adressen, ohne Header http://. Auch die echte Besucheradresse kommt an: Der Apache im Image übernimmt die IP aus X-Forwarded-For, wenn die Anfrage aus einem privaten Netz kommt. Im Test sah WordPress dann die Adresse aus dem Header statt der Adresse des Docker-Netzes.
Genau deshalb gehört der Port nur an 127.0.0.1. Wäre er öffentlich erreichbar, könnten Angreifer beide Header selbst setzen und WordPress eine falsche Herkunft vorgaukeln. Eine passende Nginx-Konfiguration für den Proxy enthält mindestens:
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 64m;
}
client_max_body_size muss zur PHP-Grenze passen, sonst lehnt Nginx große Uploads ab, bevor sie WordPress erreichen.
Verifizieren: Im Quelltext der Startseite beginnen alle Adressen mit https://. Der Website-Zustand meldet keinen Hinweis zu fehlendem HTTPS. Von einem anderen Rechner aus ist http://IHRE-SERVER-IP:8080 nicht erreichbar.
Schritt 5: E-Mail, WP-CLI und Updates
E-Mail: Das offizielle Image kann keine E-Mails versenden, das steht ausdrücklich in seiner Beschreibung. Im Labor antwortete wp_mail() mit „Die E-Mail-Funktion konnte nicht instanziiert werden.“ Passwort-Links, Formularbenachrichtigungen und Update-Meldungen gehen damit verloren. Richten Sie deshalb gleich nach der Installation ein SMTP-Plugin mit dem Postfach Ihrer Domain ein.
WP-CLI: Für Kommandozeilenarbeit gibt es eigene Image-Varianten, etwa wordpress:cli-2.12.0-php8.4. Sie basieren auf Alpine, wo der Webserver-Benutzer eine andere Nummer hat als im Debian-basierten Hauptimage. Die Image-Beschreibung empfiehlt deshalb --user 33:33, sonst gehören neu angelegte Dateien dem falschen Benutzer:
Ergänzen Sie dazu unter services einen eigenen Dienst, der nur bei Bedarf läuft:
wordpress-cli:
image: wordpress:cli-2.12.0-php8.4
profiles: ["cli"]
user: "33:33"
environment:
HOME: /tmp
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
secrets: [db_password]
volumes:
- wp_data:/var/www/html
Über das Profil startet der Dienst nicht mit docker compose up, sondern nur, wenn Sie ihn aufrufen:
sudo docker compose run --rm wordpress-cli wp plugin list
Updates: Hier liegt der häufigste Denkfehler. Laut Image-Beschreibung kopiert das Image WordPress nur beim ersten Start ins Volume, danach verwaltet sich die Installation im Volume selbst. Ein neuer Tag in der Compose-Datei aktualisiert also PHP und Apache, nicht aber WordPress, Plugins oder Themes. Diese aktualisieren Sie wie gewohnt im Backend unter Dashboard > Aktualisierungen oder per WP-CLI. Sie pflegen damit zwei Ebenen: das Image für PHP und Webserver und die Installation im Volume.
Diese doppelte Pflege ist der Preis für den eigenen Server. Wer den Server selbst betreibt, die WordPress-Ebene aber abgeben möchte, kann das tun: Die WordPress-Wartung von wordpressupdate.de übernimmt Updates von WordPress, Plugins und Themes mit Kompatibilitätsprüfung und wöchentliche Backups auf externen Speicher.
Verifizieren: Ein Test über ein SMTP-Plugin kommt im Postfach an. sudo docker compose run --rm wordpress-cli wp core version zeigt die installierte WordPress-Version, die nach einem Backend-Update steigt, auch wenn der Image-Tag gleich bleibt.
Schritt 6: Backups der Volumes einrichten
Alles Wichtige liegt in zwei Volumes: die Datenbank in db_data, Uploads, Plugins, Themes und wp-config.php in wp_data. Kopieren Sie das Datenbank-Volume nicht im laufenden Betrieb, sondern erzeugen Sie einen Dump. Im MariaDB-Image heißt das Werkzeug mariadb-dump, ein mysqldump gibt es dort nicht:
sudo docker compose exec db sh -c \
'mariadb-dump -uroot -p"$(cat /run/secrets/db_root_password)" --single-transaction wordpress' \
> /opt/backup/wordpress-$(date +%F).sql
sudo docker run --rm -v wordpress_wp_data:/data:ro -v /opt/backup:/backup \
debian:stable-slim tar -czf /backup/wp-files-$(date +%F).tar.gz -C /data .
Übertragen Sie beide Dateien auf einen zweiten Speicherort außerhalb des Servers. Wie Sie das automatisieren und prüfen, beschreiben Server-Backup per Cronjob und Restore-Probe für WordPress-Backups.
Verifizieren: Die SQL-Datei enthält Zeilen mit CREATE TABLE, das Archiv lässt sich mit tar -tzf auflisten und enthält ./wp-config.php.
Typische Fehler
- „Fehler beim Aufbau einer Datenbankverbindung“ direkt nach dem Start: Meist fehlt der Healthcheck oder die Bedingung
service_healthy, und WordPress startet vor der Datenbank. Ebenso möglich: Das Passwort in der Secret-Datei wurde nach dem ersten Start geändert. MariaDB legt Benutzer nur beim ersten Start mit leerem Volume an. - Container startet nicht, Zugriff auf Secret verweigert: Die Secret-Dateien sind für den Container-Benutzer nicht lesbar. Ordner auf 700, Dateien lesbar lassen.
- Weiterleitungsschleife oder gemischte Inhalte: Der Proxy sendet
X-Forwarded-Protonicht mit. Header ergänzen. - Upload bricht mit „413 Request Entity Too Large“ ab: Die Grenze des Reverse Proxy ist kleiner als die PHP-Grenze.
- Dateien gehören nach WP-CLI-Aufrufen dem falschen Benutzer: CLI-Container ohne
--user 33:33gestartet. mysqldump: command not found: Im MariaDB-Imagemariadb-dumpverwenden.
Häufige Fragen
Apache- oder FPM-Variante?
Die Apache-Variante bringt Webserver und PHP in einem Container und wertet .htaccess aus, das erwarten viele Plugins. Die FPM-Variante braucht einen zusätzlichen Webserver-Container. Für eine einzelne Unternehmenswebsite ist Apache der einfachere Weg.
Kann ich latest verwenden?
Für Tests ja, für den Betrieb nicht. Mit latest kann ein normales Pull eine neue PHP-Hauptversion bringen, mit der ein Plugin nicht zurechtkommt. Mit festen Tags entscheiden Sie selbst, wann Sie wechseln.
Lohnt sich Docker gegenüber klassischem Webhosting?
Wenn Sie ohnehin einen Server betreiben oder mehrere Dienste darauf laufen, ja. Wer nur eine Website hat und keinen Server pflegen möchte, ist mit gutem Webhosting oft besser bedient. Eine klassische Installation ohne Container zeigt WordPress nativ auf Ubuntu.
Testumfang
Wir haben die Bausteine dieser Anleitung auf einer Testinstallation mit WordPress 7.1.2 im offiziellen Image ausprobiert, und sie liefen wie beschrieben. Aufgefallen ist vor allem, dass der E-Mail-Versand ohne Zusatz-Plugin schlicht nicht funktioniert. Die komplette Compose-Datei haben wir nicht auf einem öffentlichen Server gestartet und keinen Reverse Proxy mit echtem Zertifikat eingerichtet. Prüfen Sie den ersten Start deshalb in Ruhe mit den Kontrollen aus jedem Schritt, bevor Sie die Domain umstellen.
Fazit
Mit festen Image-Tags, Secrets in Dateien, einem nur lokal erreichbaren Port und einem Reverse Proxy davor läuft WordPress in Docker Compose stabil und nachvollziehbar. Planen Sie von Anfang an ein SMTP-Plugin und regelmäßige Dumps ein, und behalten Sie im Kopf, dass Image-Updates und WordPress-Updates zwei getrennte Aufgaben sind. Wenn Sie die WordPress-Seite davon abgeben möchten, übernimmt das die WordPress-Wartung mit persönlicher Betreuung.
Weiterführende Anleitungen und Quellen
Weiterführende Anleitungen:
- Nginx als Reverse Proxy mit TLS einrichten
- VPS absichern und härten
- WordPress-Backup per Cronjob auf dem Server
- WordPress nativ auf Ubuntu installieren
Quellen:


