Zum Inhalt springen

Docker-Container mit WordPress inkl. SMTP-Client

Ziel ist es, WordPress mittels Docker auf einem eigenen Server zu installieren.

Wir werden zuerst auf einem bestehenden Linux-Computer (Host mit z.B. Ubuntu) Docker installieren. Dann erstellen wir ein eigenes Dockerfile, welches ein offizielles WordPress-Image um mSMTP erweitert, damit WordPress E-Mails versenden kann. Als Datenbank verwenden wir die bereits existierende MySQL-Datenbank auf dem Host.

Docker auf dem Host installieren

~ $ sudo apt-get update
~ $ sudo apt install docker.io
~ $ sudo systemctl enable --now docker
~ $ sudo apt upgrade
~ $ sudo usermod -aG docker markus
~ $ sudo systemctl reboot
~ $ groups
markus adm cdrom sudo dip plugdev users docker

Zeile 5 ist optional und gibt dem Linux-User markus administrative Privilegien für Docker.

Zeile 8 gibt die Linux-Gruppen des aktuellen Benutzers (markus) aus. Hier ist es wichtig, dass der User nun in der Gruppe docker ist.

Docker ausprobieren (Hello World)

Wir starten nun ein Hello World, um zu sehen, ob Docker grundsätzlich funktioniert.

~ $ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
0e03bdcc26d7: Pull complete
Digest: sha256:1a523af650137b8accdaed439c17d684df61ee4d74feac151b5b337bd29e7eec
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.
...

Bei Docker müssen wir zwischen Image und Container unterscheiden. Ein Image enthält praktisch alle Dateien, um daraus einen Container zu erstellen. Ein Container wird dann, vereinfacht gesagt, wie ein eigener Computer gestartet.

Der Befehl docker run erstellt aus dem Image „hello-world“ einen Container und startet ihn dann. Docker führt dann in dem Container einen Befehl aus. Sobald der Befehl abgeschlossen ist, wird der Container beendet. Später werden wir im WordPress-Container einen Apache WebServer starten, der sich nicht beendet, sondern dauerhaft läuft.

Docker kann das Image hello-world auf dem Host nicht finden und lädt ihn automatisch aus dem Internet (siehe https://hub.docker.com/search?q=&type=image ).

Alle vorhandenen Container auflisten:

~/dockerbuilds/wordpress_lina $ docker ps --all
CONTAINER ID   IMAGE        COMMAND    CREATED          STATUS                      PORTS    NAMES
174d8abe9628   hello-world  "/hello"   14 seconds ago   Exited (0) 13 seconds ago            goofy_blackwell

Hier sehen wir, dass aus dem Image hello-world ein Container mit dem automatisch generierten Namen goofy_blackwell erstellt wurde. Docker führt beim Starten des Containers den Befehl /hello innerhalb des Containers aus. Der Befehl (und damit auch der  Container) wurde vor 13 Sekungen beendet (Exited 13 seconds ago). Wir werden dem WordPress-Container später einen Namen geben, damit er nicht automatisch generiert wird.

Alle lokal vorhandene Images auflisten:

 $ docker images
REPOSITORY                   TAG                 IMAGE ID       CREATED        SIZE
hello-world                  latest              d1165f221234   2 months ago   13.3kB

Datenbank vorbereiten (MySQL)

WordPress braucht eine Datenbank. Wir haben bereits eine MySQL-Datenbank auf dem Host installiert. In meinem Fall ist es keine MariaDB, sondern eine MySQL. Damit später der Zugriff aus dem Container auf die MySQL-DB funktioniert, müssen wir noch eine Änderung daran konfigurieren, denn seit MySQL Version 8 ist die Default-Authentication-Method nicht mehr mysql_native_password, sondern caching_sha2_password. Das führt beim Zugriff auf die Datenbank zur Fehlermeldung „Plugin caching_sha2_password could not be loaded“.

Wir stellen in der mysqld.cnf den Default auf mysql_native_password:

~ $ sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
~ $ cat /etc/mysql/mysql.conf.d/mysqld.cnf|grep default-authentication-plugin
default-authentication-plugin=mysql_native_password
~ $ sudo systemctl restart mysql.service

Nun können wir in der MySQL-Datenbank eine leere Datenbank (Schema) sowie einen Datenbank-Benutzer erstellen. „passwort“ muss natürlich durch ein ausreichend starkes Passwort ersetzt werden! Gut sind hier die Passwort-Generatoren von Passwort-Managern wie insb. KeepassXC.

~ $ sudo mysql
mysql> CREATE DATABASE my_wordpress;
mysql> CREATE USER 'wordpress_user'@'172.17.0.0/255.255.0.0' IDENTIFIED BY 'passwort';
mysql> GRANT ALL ON my_wordpress.* TO 'wordpress_user'@'172.17.0.0/255.255.0.0';
mysql> use mysql
mysql> select Host, User, plugin from user;
+---------------------------+------------------+-----------------------+
| Host                      | User             | plugin                |
+---------------------------+------------------+-----------------------+
| 172.17.0.0/255.255.0.0    | wordpress_user   | mysql_native_password |
+---------------------------+------------------+-----------------------+
mysql> exit

Die letzten Befehle müssen ergeben, dass mysql_native_password verwendet wird. Der WordPress-Container wird später in einem Netzwerk-Segement 172.17.x.x laufen. Daher schränken wir den Datenbankbenutzer darauf ein. Man kann sich nur aus diesem Netz mit dem Benutzer an der Datenbank anmelden.

Eigenes Image erstellen (Dockerfile)

Im offiziellen WordPress-Image fehlt ein SMTP-Client, mit dem WordPress E-Mail versenden kann. Nur mit einem SMTP-Client kann ein WordPress-Benutzer sein Passwort zurücksenden, denn dafür muss WordPress dem Benutzer eine E-Mail senden, in dem ein Link enthalten ist, mit dem ein neues Passwort gesetzt werden kann.

Dockerfile und weitere Dateien erstellen:

~ $ nano Dockerfile
~ $ mkdir mSMTP
~ $ nano mSMTP/aliases
~ $ nano mSMTP/mail.rc
~ $ nano mSMTP/msmtprc

Dockerfile:

FROM wordpress:php7.4-apache
RUN apt-get update && apt-get install -y lsb-release nano msmtp msmtp-mta mailutils
RUN echo "export LS_OPTIONS='--color=auto'" >> /root/.bashrc
RUN echo "alias ll='ls --color=auto -al'" >> /root/.bashrc
COPY --chown=root:msmtp ./mSMTP/msmtprc /etc/msmtprc
RUN chmod 640 /etc/msmtprc
COPY --chown=root:root ./mSMTP/aliases /etc/aliases
COPY --chown=root:root ./mSMTP/mail.rc /etc/mail.rc

Zeile 1: Das eigene Image, welches wir anhand des Dockerfiles erstellen, basiert auf dem Image, welches mit FROM angegeben ist. Es ist in diesem Fall die WordPress-Variante mit PHP in Version 7.4 und Apache WebServer. Hier könnte man auch andere angeben. Siehe auch https://hub.docker.com/_/wordpress.

Zeile 2: Hier installieren wir weitere Linux-Pakete. lsb-release, nano und mailutils werden eigentlich von WordPress nicht benötigt und können entfallen. Es vereinfacht den Umgang mit dem Container später etwas. lsb-release zeigt einem die genaue Linux-Distribution des Containers. Nano ist ein einfacher Editor zum Ändern von Konfigurationsdateien. Mailutils enthält den Mail-Client „mail“, mit dem man aus dem Container zum Testen E-Mails versenden kann. msmtp und msmtp-mta sind die eigentlichen Pakete unseres SMTP-Clients, den wir zum Versenden von E-Mails benötigen.

Zeilen 3 und 4 aktivieren die farbliche Anzeige in der Console und setzen einen Alias. Dadurch hat man den Befehl „ll“, der dann den Befehl „ls -al“ ausführt. Das wird von WordPress ebenfalls nicht verwendet.

Zeilen 6 bis 8 kopieren Dateien in unser Image (und damit auch in den Container). Das sind die Konfigurationsdateien für mSMTP.

aliases:

root: wordpress@example.de
default: wordpress@example.de

E-Mails an Linux-User sollen an die oben angegebenen externen E-Mail-Adressen weitergeleitet werden.

mail.rc:

set sendmail="/usr/bin/msmtp -t"

Sendmail soll den mSMTP-Client zum Versenden nutzen.

msmtprc:

defaults
logfile ~/.msmtp.log
auth on
tls on

account strato
host smtp.strato.de
port 587
from wordpress@example.de
user wordpress@example.de
password geheimespasswort

account default: strato
aliases /etc/aliases

In msmtprc müssen die Werte des externen SMTP-Servers eingetragen werden. mSMTP sendet ausgehende E-Mails dann an diesen Server. In meinem Beispiel ist es ein SMTP-Server bei Strato.

Zeile 2: Hier schreibt mSMTP das Logfile. Damit finden sich Fehler, falls das Versenden von E-Mails nicht funktioniert.

Zeile 6: strato ist der Name der Account-Definition. Er kann selber vergeben werden und muss mit dem Namen in Zeile 13 übereinstimmen.

Zeilen 7 bis 11: Hier müssen die individuellen Daten des SMTP-Server angegeben werden. User und Passwort sind die Zugangsdaten beim jeweiligen Provider.

Nun erstellen wir unser eigenes WordPress-Image:

~ $ docker build -t my_wordpress_img .
...
Successfully built b365b8fe4a51
Successfully tagged my_wordpress_img:latest

~ $ docker images
REPOSITORY       TAG    IMAGE ID     CREATED     SIZE
my_wordpress_img latest b365b8fe4a51 3 hours ago 660MB

Als Tag verwendet Docker automatisch latest. Hier könnten wir auch selber ein Tag angeben.

Docker-Container erstellen und starten

Mit docker run erstellen wir aus unserem selbst erstellen Image einen Container und starten ihn.

 $ docker run -v my_wordpress-vol:/var/www/html --restart unless-stopped --name my_wordpress_container -p 8380:80 -d -e WORDPRESS_DB_HOST=172.17.0.1 -e WORDPRESS_DB_NAME=my_wordpress -e WORDPRESS_DB_USER=wordpress_user -e WORDPRESS_DB_PASSWORD=passwort my_wordpress_img

my_wordpress-vol ist ein Docker-Volume. Das ist ein Bereich auf der Festplatte des Hosts, der im Cointainer bei /var/www/html eingehangen wird. Beim ersten Start des Containers legt das WordPress-Image die eigentlichen WordPress-Dateien in den Ordner /var/www/html ab. Durch das Volume werden sie dann tatsächlich außerhalb des Container gespeichert. Das bewirkt, dass man den Container löschen und wieder neu erstellen kann, ohne seine WordPress-Installation zu verlieren. Nachträglich über die WordPress-Oberfläche installierte Plugins werden ebenfalls unterhalb dieses Ordners abgespeichert.

–restart unless-stopped bewirkt, dass der Container automatisch gestartet wird, außer man stoppt ihn manuell selber. Das hat den Vorteil, dass nach einem Neustart des Hosts der Container von Docker automatisch wieder gestartet wird.

–name my_wordpress_container definiert den Namen des erstellten Containers

-p 8380:80 bewirkt, dass WordPress auf dem Host über den Port 8380 erreichbar ist. Im Container wird ein Apache WebServer gestartet, der auf Port 80 läuft. Man kann selber den äußeren Port ändern. Der Port darf noch nicht von etwas anderem belegt sein.

-d startet den Container im Hintergrund (detached).

-e WORDPRESS_DB_HOST=172.17.0.1 gibt die IP-Adresse der MySQL-Datenbank an. Es ist die IP-Adresse das Netzwerk-Gateways zum Host.

-e WORDPRESS_DB_NAME=wordpress ist der Name der Datenbank (Schema). Den haben wir oben beim CREATE der Datenbank vergeben.

-e WORDPRESS_DB_USER=wordpress Ist der Benutzername für die Anmeldung an der Datenbank.

-e WORDPRESS_DB_PASSWORD=passwort ist das Passwort für die Anmeldung an der Datenbank und muss noch durch das oben zufällig generierte Passwort ersetzt werden (siehe Passwort-Manager / KeepassXC).

my_wordpress_img ist der Name des von uns selbst erstellten Images.

 

Jetzt können wir bereits WordPress mit einem Browser aufrufen: http://host:8380

host muss noch durch die IP-Adresse oder den Namen des Host-Computers ersetzt werden, auf dem Docker installiert wurde.

Falls WordPress nicht auf die Datenbank zugreifen kann, dann wird direkt die Fehlermeldung „Error establishing a database connection“ im Browser angezeigt. Dann bitte prüfen, ob beim Anlegen der Datenbank und dem Erstellen des Datenbank-Benutzers etwas anderes als beim docker run angegeben wurde. Wenn es übereinstimmt, kann WordPress auf die Datenbank zugreifen, die anfangs noch leer ist.

Wenn der Zugriff auf die Datenbank funktioniert, dann startet die Erstkonfiguration von WordPress. Zuerst wird die Sprache abgefragt.

Auf der nächsten Seite gibt man den Namen der WordPress-WebSite an. Hier gibt man zudem Name und Kennwort des ersten Benutzers an, der dann der Administrator der WordPress-WebSite ist.

Danach wird WordPress eingerichtet und die Datenbank befüllt.

 

Nützliche Befehle

# Logausgaben des Container kontinuierlich ausgeben
docker logs -f my_wordpress_container

# Details zum Container anzeigen
docker inspect my_wordpress_container|less

# Container auflisten
docker ps --all

# Container stoppen
docker stop my_wordpress_container

# Container starten
docker start my_wordpress_container

# Container löschen (sollte man einfach wieder neu erstellen
# können, wenn man keine manuellen Änderungen am Container
# vorgenommen und anderes auf ein Volume ausgelagert hat.)
docker rm my_wordpress_container
# oder
docker container rm my_wordpress_container


# Details zum Images anzeigen
docker inspect my_wordpress_img|less

# Images auflisten
docker images

# Image löschen (kann einfach mittels Dockerfile neu erstellt werden)
docker rmi my_wordpress_img


# Volumes auflisten
docker volume ls

# Volume löschen (VORSICHT, lässt sich ohne Backup nicht rekonstuieren)
docker volume rm my_wordpress-vol


# Image aus Container erstellen (sollte nur im Ausnahmefall benötigt werden,
# weil in Containern keine Daten liegen sollten, die nicht wiederherstellbar
# sind, denn Nutzdaten sollten in Volumes oder einer Datenbank liegen)
docker commit <container-id> <image-name>:tag
# Beispiel:
docker commit 1d68e432aa06 my_wordpress-container:copy01

Weiteres

Weitere Themen rund um WordPress werde ich hier nur anschneiden.

Reverse Proxy

Aus Sicherheitsgründen sollte man WordPress nicht direkt aus dem Internet erreichbar machen. Es ist besser einen WebServer (z.B. Apache2) mit Reverse Proxy davor zu schalten. Ein weitere Vorteil ist, dass man mehrere Apaches (z.B. mehrere WordPress oder noch andere WebSites) hinter dem Reverse Proxy betreiben kann und alle nach außen den gleichen SSL-Port 443 verwenden können. Es ist möglich, mehrere Sub-Domains zu verwenden.

Let’s Encrypt SSL-Zertifikat

Bei der Anmeldung werden Benutzername und Kennwort über das Internet gesendet. Deshalb muss die Verbindung unbedingt verschlüsselt werden (https). Heutzutage bekommt man von Let’s Encrypt kostenlos SSL-Zertifikate, die dann für eine Verschlüsselung verwendet werden. Man kann auf dem vorgeschalteten WebServer, auf dem ein Reverse Proxy läuft, SSL mit Let’s Encrypt konfigurieren und dort SSL terminieren.

Siehe Blog-Eintrag: Apache2 mit Let’s Encrypt SSL-Zertifikat und ReverseProxy installieren

Backup

  • Volumes
  • Datenbankinhalte
  • Dockerfile mit benötigten Dateien zum Generieren der eigenen Images

Eine Datensicherung muss von der Datenbank, dem Volume und den Dateien für das Build von Images (Dockerfile,…) gemacht werden. Für die Datenbank könnte man auf dem Host z.B. automysqlbackup nutzen. Es sichert regelmäßig die Inhalte der MySQL-Datenbank ins Dateisystem. Davon muss man dann nur noch die Dateien auf einem anderen Computer sichern. Das lässt sich automatisieren.

Die Volumes sind auf dem Host im Ordner /var/lib/docker/volumes gespeichert. Diesen Ordner sichert man am besten regelmäßig zusammen mit den von automysqlbackup erstellten SQL-Dumps.

Weitere Details zum Einrichten eines automatisierten Backups sind jetzt nicht mehr hier im Scope. Das gute an Docker ist, dass die Images und Container schnell wieder erzeugt werden können. Wer mehr Sicherheit möchte, exportiert zumindest noch die Basis-Images, also die Images, aus denen wir mit Dockerfiles unsere Images erstellt haben. Ich persönlich mache das nicht. Falls man sich nicht sicher ist, ob sich Container rekonstruieren lassen, weil dort evtl. doch außerhalb der Datenbank oder Volumes nicht wieder herstellbares liegt, der kann sich von den Containern Images erstellen und diese Images exportieren, um sie dann zu sichern. Ich jedenfalls lösche mir Container und Images beim Aufsetzen immer wieder, um genau das Problem auszuschließen.

Schreibe einen Kommentar