Voraussichtliche Lesedauer: 13 Minuten

Vor einiger Zeit ist meine WordPress Webseite nach einem fehlgeschlagenen Plugin-Update fatal gecrasht. Das wirklich fatale daran war, dass ich es überhaupt nicht mitbekommen habe. Mehr als einen Tag bekam man beim Aufruf meiner Webseite nur einen Fehler angezeigt. Aber warum habe ich keine Information darüber erhalten? Das liegt leider am Docker Image von WordPress, welches es standardmäßig nicht ermöglicht E-Mails zu versenden.

Recovery Mode funktioniert in Docker nicht Out-of-the-box

Eigentlich gibt es den integrierten Recovery Mode von WordPress, der eine E-Mail an euch sendet, wenn etwas nicht stimmt. Wenn ihr WordPress aber mit dem Standard Docker Image am laufen habt, dann klappt das leider nicht von Haus aus. Fixen kann man das Ganze aber durch einige Konfigurationen.

Ich habe bereits mehrere Prüfmethoden auf meinen Services wie einen Healthy Check auf den Docker Containern und auch durch Uptime Kuma einen regelmäßigen Test, ob die URL noch erreichbar ist. Beides hatte nicht gemeckert, weil die Webseite nach wie vor über die URL erreichbar war. Das aber eben nur als ein kleines Fenster mit einem Fehlercode. Da der Statuscode der Webseite dabei 20x bleibt, also beispielsweise 200 für OK, blieb das Ganze unentdeckt und gab mir schließlich zu Denken.

In den Logs des WordPress Containers entdeckte ich dann das Logging der Fehler und ganz wichtig einen Eintrag:

sh: 1: /usr/sbin/sendmail

Damit wurde schnell klar, dass versucht wurde eine E-Mail an mich zu senden, aber das nötige Package inklusive Konfiguration dafür gar nicht in dem Docker Image installiert ist. Bis zu diesem Zeitpunkt dachte ich immer es würde auch bei Fehlern ausreichen ein Plugin wie WP Mail SMTP installiert zu haben oder allgemein funktionieren, dass man E-Mails der WordPress Instanz erhält. Im Nachhinein betrachtet macht dies nur wenig Sinn, da es auch ein Plugin sein könnte das fehlerhaft ist und WordPress außerdem selbst nie eine Mailkonfiguration von mir erhalten hat.

Würde der Recovery Mode funktionieren würde man anstatt gar keiner Information eine E-Mail mit Hinweisen zum Fehler bekommen wie in dem nachfolgenden Bild zu sehen ist. Leider wissen vermutlich viel zu wenige Betreiber und Nutzer, dass die Recovery Mode Funktion von WordPress in Docker nicht ohne Aufwand funktioniert.

WordPress Recovery Mode E-Mail mit Informationen
WordPress Recovery Mode E-Mail mit Informationen über einen Fehler

Der Recovery Mode ist zwar von der Auslieferung heraus eingeschaltet, aber kann diese E-Mail standardmäßig aufgrund des fehlenden sendmail Package nicht versenden. Dabei verpasst man wichtige Informationen über Probleme mit der Webseite. Ich habe auf dem Bild persönliche Informationen ausgeblendet.

Wie kann man E-Mails aus dem Docker Container verschicken?

Anhaltspunkt war für mich nach dem Fehler natürlich das Package sendmail, welches sich aber überhaupt nicht einfach installieren beziehungsweise nutzen lies. Das Problem scheint ein allgemeines Problem bei PHP-Anwendungen zu sein, wenn sie denn auch als Docker Image verfügbar sind. Es gibt wohl einige Lösungen in denen man noch einen weiteren Docker Container mit einem anderen Image nur für Mails zur Verfügung stellt. Dies kann Sinn machen, wenn man mehrere PHP-Anwendungen hat und nicht überall das Image anpassen möchte. Für mich war es jedenfalls keine zufriedenstellende Lösung noch einen Container zur Verfügung zu stellen, der wieder Speicher in Anspruch nimmt und dauerhaft läuft.

Dockerfile erstellen

Dementsprechend besteht der erste Schritt der Lösung darin, dass wir uns ein eigenes Dockerfile für WordPress basteln müssen. Anders als erwartet fügen wir aber nicht sendmail hinzu, sondern umgehen s Package. Das Dockerfile sollte dann folgendermaßen aussehen:

FROM wordpress:latest

RUN \
    apt-get update && \
    apt-get install -y --no-install-recommends \
    libicu-dev  \
    libldap2-dev \
    ssmtp && \
    docker-php-ext-install intl && \
    docker-php-ext-enable intl && \
    docker-php-ext-install shmop && \
    docker-php-ext-enable shmop

# modify ssmtp settings
RUN sed -ri -e 's/^(mailhub=).*/\1smtp-server/' \
    -e 's/^#(FromLineOverride)/\1/' /etc/ssmtp/ssmtp.conf

RUN \
    pecl install apcu && \
    docker-php-ext-enable apcu

Das ist ein wenig viel auf einmal und auch einiges an Code, den ihr vermutlich nicht benötigt. Aber schaden kann es auch auf keinen Fall. Wir nutzen das aktuellste Image von WordPress als Basis und laden uns dann erst einmal die Informationen über die neuesten Packages, um sie dann anschließend ohne irgendwelche Eingabeaufforderungen, die wir beim Build nicht beantworten könnten, mit apt-get install -y --no-install-recommends zu installieren. Der Backslash \ am Ende einer Zeile dient hauptsächlich der Lesbarkeit. So kann man die Zeile unterbrechen ohne den Befehl zu unterbrechen.

Für die Mail Funktionalität sind lediglich die Installation des ssmtp Package und des Befehls RUN sed -ri -e 's/^(mailhub=).*/\1smtp-server/' \ -e 's/^#(FromLineOverride)/\1/' /etc/ssmtp/ssmtp.conf notwendig. Die restlichen installierten Packages dienen dazu OPcache und APCu zu installieren. Diese beiden helfen bei der Performanz der Webseite. OPcache ist ein PHP-Modul, das den PHP-Code der Webanwendung (beispielsweise Skripte) im Arbeitsspeicher zwischenspeichert. APCu macht im Grunde genommen dasselbe, aber ist für einen anderen Teil an Daten zuständig.

Zurück beim Thema müssen wir das Image nun auch mit dem Dockerfile erstellen. Den Inhalt speichert ihr einfach in einer Datei namens Dockerfile. Dabei keine Dateiendung hinzufügen. Diese Datei speichert ihr dann idealerweise auf eurem Server dort, wo ihr auch eure docker-compose.yml für WordPress liegen habt.

Werte für Postfix auslesen

Es ist elementar, dass ihr auf eurem Server das Package postfix installiert und so konfiguriert habt, dass ihr E-Mails damit versenden könnt. Das benötigt ihr unter anderem ohnehin für Unattended Upgrades, um bei automatischen Updates auf eurem Server informiert zu werden. Für den nachfolgenden Teil wird angenommen, dass ihr den postfix installiert und soweit eingerichtet habt, dass E-Mails versendet werden können.

Auch wenn wir durch das Dockerfile jetzt das nötige Package installiert haben, dass das Versenden der E-Mails möglich macht, gibt es dennoch Probleme, die es aus Docker heraus nicht möglichen machen dies zu tun. Würdet ihr das nun machen, würdet ihr den Fehler 554 5.7.1 Relay access denied erhalten. Das liegt daran, dass Docker und insbesondere hier der WordPress Docker Container nicht dazu berechtigt sind E-Mails über Postfix zu versenden. Dementsprechend müssen wir die Konfiguration anpassen.

Wir haben zwei Möglichkeiten für die Konfiguration von inet_interfaces. Wir können den Wert auf all setzen und damit alle eingehenden Verbindungen akzeptieren oder wir müssen spezifisch festlegen, welche Netzwerk Schnittstellen akzeptiert werden sollen. Mit ip link show könnt ihr euch alle Schnittstellen anzeigen lassen:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 00:1e:06:42:24:a3 brd ff:ff:ff:ff:ff:ff
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
    link/ether 02:42:d2:7c:62:3c brd ff:ff:ff:ff:ff:ff

Nun ist es wichtig, dass wir herausfinden, welche IP-Adresse hinter den jeweiligen Netzwerk Schnittstellen steckt. Das können wir mit dem Befehl ifconfig <Schnittstellenname> also bspw. ifconfig docker0 herausfinden. Die Ausgabe sieht dann so aus;

docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:d2ff:fe7c:623c  prefixlen 64  scopeid 0x20<link>
        ether 02:42:d2:7c:62:3c  txqueuelen 0  (Ethernet)
        RX packets 2710  bytes 159432 (159.4 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 3090  bytes 39984102 (39.9 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Hier ist der Wert, der bei inet steht relevant. Dieser kann bei euch anders sein. Nutzt den Wert, den euch euer System ausgibt. Bei dem lo (loopback/localhost) Adapter wird die IP-Adresse eigentlich immer 127.0.0.1 sein, aber überprüft auch das. Merkt euch für alle Adapter, die ihr erlauben wollt, die IP-Adresse bzw. speichert sie irgendwo zwischen.

Docker Netzwerk / Container freigeben

Der kompliziertere Teil folgt nun. Wir müssen auch den Docker Container oder sein Netzwerk freigeben. Das kann sehr individuell sein. Ich zum Beispiel nutze ein separat definiertes Netwerk für meinen WordPress Container in der docker-compose.yml. Dementsprechend ändert sich auch bei jedem Stoppen und Starten die Netzwerkadresse. Wenn ihr kein Netzwerk angegeben habt, dann findet ihr den Container meist in einem Netzwerk mit dem Namen <xyz>_default wieder. Dies könnt ihr euch am einfachsten in Portainer anschauen. Ansonsten müsst ihr ein paar Befehle eingeben, um es zu herausfinden (unter anderem docker inspect <mein_wordpress_container>.

Relevant ist für uns davon die IPV4 IPAM Subnet Adresse. Bei meinem Netwerk storage_wordpress ist es 172.19.0.0/16. Idealerweise setzen wir diese Subnet Adresse nun auch statisch fest damit diese sich nicht mehr ändert und unsere Konfiguration auch immer valide ist und funktioniert. Dazu geht ihr nun in eure docker-compose.yml Datei für WordPress. Ihr müsst nun neben den services: einen weiteren Punkt hinzufügen.

networks:
  wordpress:
    ipam:
      config:
        - subnet: 172.19.0.0/16

Das könnt ihr auch machen, wenn euer Netzwerk in dem der WordPress Container ist default heißt. Wichtig ist außerdem, dass ihr das Netzwerk, falls ihr es nicht schon habt, auch zum Container hinzufügt. Das solltet ihr dann übrigens auch für die zugehörige Datenbank machen, damit die beiden weiterhin miteinader kommunizieren können.

  wordpress:
    container_name: wordpress
    networks:
      - wordpress
    ...

Noch brauchen wir die Compose Datei nicht neustarten, da wir noch nicht fertig sind. Das machen wir ganz zum Schluss.

Werte in Postfix übertragen

Wir editieren nun die Konfiguration in Postfix mit dem Befehl sudo nano /etc/postfix/main.cf. Dort gebt ihr nun bei inet_interfaces = all oder inet_interfaces = <eure localhost IP>, <eure docker0 IP> an. Die Werte werden durch Komma getrennt. Das Docker Netzwerk können wir nun bei dem Punkt mynetworks eintragen. Lasst bestehende Werte wie sie sind und fügt ein Komma mit eurer nachfolgenden IPV4 IPAM Subnet Adresse hinzu. Das sieht bei mir so aus:

mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128, 172.19.0.0/16

Anstatt der IP-Adresse des Netzwerks kann man auch nur eine direkte IP-Adresse eines Containers angeben. Dies würde genauso funktionieren. Aber nach Neustarten von Containern ist die Gefahr sehr groß, dass wenn der Container nicht auch eine statische IP-Adresse zugewiesen bekommen hat, dass der Wert sich ändert und die Konfiguration unbrauchbar ist.

Wir halten fest, dass wir in der Datei /etc/postfix/main.cf lediglich die Werte der Zeilen inet_interfaces und mynetworks anpassen und damit hier fertig sind. Abschließend müssen wir Postfix neustarten damit die Änderungen übernommen werden. Das tun wir mit dem Befehl sudo systemctl restart postfix.

SMTP in php.ini konfigurieren

Damit unser Dockerfile nun auch wirklich zu tragen kommt, müssen wir WordPress noch sagen, worüber es die E-Mails noch versenden soll. Der Befehl RUN sed -ri -e 's/^(mailhub=).*/\1smtp-server/' \ -e 's/^#(FromLineOverride)/\1/' /etc/ssmtp/ssmtp.conf legt smtp-server als Wert fest über den der mailhub respektive Postfix erreicht werden kann.

Wenn ihr schon eine php.ini Datei auf eurem WordPress Server installiert / eingerichtet habt, dann fügt folgende Zeile hinzu:

SMTP=smtp-server

Solltet ihr die Datei noch nicht haben ist das auch kein Problem. Erstellt euch in eurem WordPress Ordner eine php.ini und verweist in der docker-compose.yml darauf:

    volumes:
      - /data/website/wordpress:/var/www/html
      - /data/website/wordpress/php.ini:/usr/local/etc/php/conf.d/php.ini

Es ist hier nur die Zeile mit php.ini relevant. Der Rest ist der Standard der WordPress Installation. Nun habt ihr auch eine aktive php.ini für eure WordPress Installation. Die benötigt ihr auch für OPCache Einstellungen oder Dateiupload Einstellungen.

Docker Compose anpassen

Wir haben WordPress zwar im Schritt davor gesagt, dass es für SMTP Verbindungen auf smtp-server schauen soll und diesen Wert auch in der php.ini hinterlegt, aber eine IP-Adresse dafür haben wir bis dato noch nicht hinterlegt. Da kommt erneut die IP-Adresse der Netzwerk Schnittstellen docker0 ins Spiel. Zur Erinnerung, diese könnt ihr über den Befehl ifconfig docker0 (inet Wert relevant) abrufen. Die vollständige docker-compose.yml (Datenbank außer Betracht gelassen) sieht folgendermaßen aus:

version: "3"
networks:
  wordpress:
    ipam:
      config:
        - subnet: 172.19.0.0/16

services:
  wordpress-db:
    container_name: wordpress-db
    image: mariadb:latest
    volumes:
      - /data/website/wordpress-db:/var/lib/mysql
    restart: always
    networks:
      - wordpress
    env_file:
      - .secrets/wordpress.env
    healthcheck:
      test: mysql --user=$$MYSQL_USER --password=$$MYSQL_PASSWORD -e 'SHOW DATABASES;'
      interval: 20s
      start_period: 10s
      timeout: 10s
      retries: 3

  wordpress:
    container_name: wordpress
    links:
      - wordpress-db
    depends_on:
      wordpress-db:
        condition: service_healthy
    image: saschabrockel/wordpress:latest
    build:
      context: /media/storage
      dockerfile: Dockerfile
    expose:
      - 80
    restart: always
    volumes:
      - /data/website/wordpress:/var/www/html
      - /data/website/wordpress/php.ini:/usr/local/etc/php/conf.d/php.ini
    networks:
      - wordpress
    env_file:
      - .secrets/wordpress.env
    extra_hosts:
      - smtp-server:172.17.0.1

Der wichtigste Punkt befindet sich in der letzten Zeile mit - smtp-server:172.17.0.1. Hier wird unter extra_hosts definiert wie die IP-Adresse vom Netzwerk Adapter docker0 lautet, um so die Verbindung zu erlauben. Ganz oben finden wir auch die Netzwerkkonfiguration wieder. Den healthcheck der Datenbank und auch depends_on mit der service_healthy Kondition könnt ihr für euch selbst außer Acht lassen. Was nun der letzte wichtige Punkte ist, ist der build Teil:

    build:
      context: /media/storage
      dockerfile: Dockerfile

Ihr müsst dort unter context den genauen Pfad angeben, wo sich euer zu Beginn erstelltes Dockerfile befindet. Und bei dockerfile gebt ihr den Namen eures Dockerfile ein. Zuletzt passt ihr noch die Zeile image an und gebt dort einen beliebigen Namen für euer eigenes Image an.

Build des eigenen WordPress Docker Image

Das Image nun mit dem Dockerfile zu builden funktioniert relativ simpel. Wir müssen lediglich unseren Startbefehl für die docker-compose.yml ein wenig ergänzen:

docker compose -f /media/storage/docker-compose.yml up -d --build

Beachtet, dass ihr euren eigenen Dateipfad und auch Dateinamen angebt. Der Parameter –build sorgt dafür, dass das Image erstellt wird. Das Ganze dauert einen kurzen Augenblick. Nun seid ihr soweit und habt euer eigenes E-Mail fähiges Docker Image erstellt.

WordPress Recovery Mode testen

Ob ihr wirklich richtig steht, seht ihr wenn das Licht angeht. Das gilt auch für diese Einrichtung hier. Wir müssen validieren, ob das Ganze funktioniert hat. Dazu werden wir idealerweise einen eigenen Fehler einbauen und uns eine E-Mail an unsere eigene E-Mail senden lassen. Standardmäßig sendet WordPress Fehler E-Mails wie die oben auf dem Bild gezeigte an die Administratoren. Wir können das Ganze überschreiben in dem wir in der wp-config.php in eurer WordPress Installation die Zeile define( 'RECOVERY_MODE_EMAIL', '[email protected]' ); ergänzen.

So weit, so gut, fehlt nur noch der Fehler. Vorab möchte ich darauf hinweisen, dass es bei mir nötig war Plugins wie Redis Object Cache auszuschalten, da ansonsten der veränderte PHP-Code nicht geladen wurde, sondern lediglich der Cache genutzt wurde.

Um einen Fehler einzubauen geht in eurer WordPress-Installation unter wp-content/themes/euerTheme in die functions.php und fügt Folgendes ein:

function justtestingstuff() {
	error_log("Oh no! We are out of FOOs!", 1, "[email protected]");
}

justtestingstuff();

Haltet alles bereit, um die Änderung direkt rückgängig zu machen und ruft dann die Webseite auf. Ihr werdet sehr schnell eine Vielzahl an E-Mails mit der definierten Fehlermeldung erhalten. Ihr könnt euch nun also sicher sein, dass alles funktioniert. Wenn ihr komplett auf Nummer sicher sein wollt, könnt ihr eine Datei in einem Plugin umbenennen und werden wahrscheinlich so schnell einen wirklichen fatalen Fehler auslösen und den Recovery Mode dazu bringen euch eine E-Mail zu senden. Dies sollte keinesfalls auf einer aktiv genutzten Webseite ausprobiert werden! Vergesst nicht die RECOVERY_MODE_EMAIL wieder rückgängig zu machen.

Fazit

Mit dieser Konfiguration seid ihr bestens über Fehler in eurer WordPress Instanz auf Docker informiert. Ihr erhaltet E-Mails vom integrierten WordPress Recovery Mode und umgeht dabei geschickt das fehlende sendmail Package mit dem in den Logs ersichtlichen Fehler sh: 1: /usr/sbin/sendmail. Nebenbei habt ihr alle Vorkehrungen geschaffen, um durch die php.ini individuelle Anpassungen zu machen und durch das Dockerfile sogar Performanzoptionen geschaffen.

Zusammengefasst müsst ihr Postfix eingerichet haben, dann ein eigenes Dockerfile erstellen, euer WordPress-Docker-Netzwerk statisch machen und in Postfix hinterlegen, eine php.ini anlegen und dort SMTP hinterlegen, eure docker-compose.yml anpassen und seid dann durch! Nun könnt ihr Mails aus eurem WordPress Docker Container senden.


0 Kommentare

Schreibe einen Kommentar

Avatar-Platzhalter

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.

de_DE