Dockeranfänger aufgemerkt! Es ist häufiger mal nützlich, alte PHP-Anwendungen in einer sicheren Umgebung zum Laufen zu bringen, am besten auch reproduzierbar. All dies ist kein Hexenwerk – und hier zeigen wir euch, wie ihr (Legacy-)PHP-Applikationen in Docker-Container verpackt.
Die Ausgangssituation
Wir hatten uns in einem internen Projekt damit beschäftigt, Tools, Skripte und andere nützliche Dinge zusammenzutragen, die uns bei einer Migration eines PHP-Projektes auf eine neuere PHP-Version helfen können.
Im Laufe dieser Arbeit wollten wir eine Beispiel-Legacy-Applikation aufsetzen. Sie sollte aus der „echten Welt“ kommen, auf Symfony basieren, aber immer noch klein und übersichtlich genug sein, um unsere erarbeiteten Tools auszuprobieren. Außerdem soll sie auch in einem Workshop Anwendung finden können.
Unsere Wahl fiel auf Wallabag, speziell Wallabag 2.0.0-beta.1, basierend auf der PHP-Version 5.6.
[smartblock id=“10413″]Docker und (Legacy-)PHP-Apps
Zu Beginn des Projekts benötigten wir dafür erst einmal zwei Dinge:
- Die Webserver-Konfiguration und
- alle Befehle, die zum Bauen der Applikation notwendig sind.
In unserem Fall hatten wir bereits eine Konfiguration für nginx; somit fiel auch die Wahl auf einen Webserver nicht schwer.
Erste Anlaufstelle für PHP-Docker-Images ist Docker Hub: https://hub.docker.com/_/php. Außerdem werden auf Docker Hub allerhand Webserver-Images angeboten; in unserem Fall haben wir uns für das nginx-Docker-Image entschieden.
In unserem Setup verwenden wir Docker Compose, um sowohl einen Webserver-Container, als auch einen Container mit unserem PHP-Projekt, aufzusetzen.
Docker Compose
Unsere docker-compose.yml
sah (für den Anfang erst mal) so aus:
version: '3' services: nginx: image: nginx ports: - "8080:80" volumes: - "./nginx.conf:/etc/nginx/nginx.conf" - ".:/var/www/html" php: build: context: wallabagContainer/ volumes: - ".:/var/www/html"
Hier machen wir es uns leicht und laden alle Dateien in unserem Verzeichnis per bind mount in beide Container hinein. Außerdem mounten wir unsere Webserver-Konfiguration nginx.conf
über die bereits im Parent-Image existierende darüber.
Der Eintrag wallabagContainer/
bezieht sich auf einen von uns angelegten Unterordner, in dem ein (so benanntes) Dockerfile
liegt.
├── docker-compose.yml ├── nginx.conf └── wallabagContainer ├── Dockerfile └── wallabag.sqlite 1 directory, 4 files
Das Dockerfile
Im Dockerfile
haben wir die Umgebung für unsere PHP-Applikation eingerichtet. Während unser nginx schon durch seine Konfigurationsdatei fertig eingerichtet wurde, müssen wir in dem Dockerfile
noch ein wenig selbst Hand anlegen:
# Image, auf dem wir aufbauen FROM php:5.6-fpm # Alter Composer für alte Applikation COPY --from=composer:1 /usr/bin/composer /usr/bin/composer ENV COMPOSER_MEMORY_LIMIT=-1 ENV SYMFONY_ENV=prod # Zeitzone (wichtig für unsere Applikation) ARG timezone='Europe/Paris' RUN echo "date.timezone="$timezone > /usr/local/etc/php/conf.d/date_timezone.ini # Externe Abhängigkeiten werden mit apt (Debian-Paketmanager) installiert RUN apt-get update && apt-get install -y \ libmcrypt-dev libicu-dev libpq-dev libxml2-dev git zip libgd3 libpng-dev \ && docker-php-ext-install \ iconv mcrypt mbstring intl pdo gd # Skript für nachträgliche Befehle im Container COPY run.sh /run.sh RUN chmod +x /run.sh # Befehle zum Bauen der Applikation RUN mkdir /app && cd /app && composer create-project wallabag/wallabag wallabag "2.0.0-beta.1" --no-dev CMD ["/run.sh"]
Wir gehen also vom vorgefertigten Image aus und installieren unsere Abhängigkeiten.
Docker-Images sind von Haus aus sehr schlank. Wenn man jedoch – wie in unserem Fall – nicht auf ein bereits existierendes, applikationsspezifisches Image zurückgreifen kann, müssen dementsprechend auch banal erscheinende Abhängigkeiten meist selbst installiert werden.
Das Bauen der Applikation
Im Dockerfile sind alle Befehle enthalten, um unsere Applikation zu bauen. In unserem Skript run.sh
verschieben wir die Applikation in den Ordner, der für unseren Webserver zugreifbar ist und zugleich mit unserem Host geteilt wird. Beachtenswert ist, dass die Datei erst ausgeführt wird, nachdem der Container bereits läuft und somit der bind mount erfolgt ist. Um das zu erreichen, verwenden wir die CMD-Direktive.
#!/bin/bash # Wenn unsere Applikation noch nicht existiert if ! [ -e "/var/www/html/wallabag" ]; then echo "Copying project to shared volume, please wait..." cp -rL /app/wallabag /var/www/html/ cp -f /var/www/html/wallabagContainer/wallabag.sqlite /var/www/html/wallabag/data/db/wallabag.sqlite echo "Project copied, FPM will start now!" fi # Starte fpm php-fpm
Hierin verschieben wir beim ersten Start das Wallabag-Projekt nach /var/www/html
. Um Wallabags interaktiven Konfigurationsprozess zu vermeiden, spielen wir eine vorgefertigte Sqlite-Datenbank ein. Dazu haben wir einfach die Datenbank einer frisch eingerichteten Wallabag-Installation kopiert.
Docker und nginx
In unserer nginx.conf
gibt es noch einen wichtigen, Docker-spezifischen Punkt:
[…] http { […] upstream php-upstream { server php:9000; } […]
In Docker Compose sehen sich die Docker-Container gegenseitig unter ihrem Namen (in unserem Fall php
) und können so miteinander kommunizieren, selbst wenn wir in unserer Docker-Compose.yml
nicht explizit ein Netzwerk definiert haben.
Hinweis
Viele Applikationen benötigen spezifische Webserver-Konfigurationen. Hier ist etwas Recherchearbeit (oder das Kopieren einer bestehenden Konfiguration) sehr wertvoll.Stolperstein Dateiberechtigungen
Theoretisch haben wir nun alles, was wir brauchen.
Allerdings hatten wir in unserem Projekt auf Unix-Systemen noch das Problem, dass wir falsche Dateiberechtigungen bei unserem PHP-Code hatten, was dazu führte, dass die Anwendung nicht lief. Außerdem sollte der Source Code noch weitergenutzt werden und auch außerhalb der Docker-Umgebung die selben Dateiberechtigungen besitzen.
Deshalb gibt es hier noch einen Punkt, der beachtet werden muss: Die korrekten Zugriffsrechte. Das heißt, eine Übereinstimmung der Nutzer-ID von Host und Gast.
Herausforderungen unter Unix entgegenwirken
Auf Unix-Systemen erzeugt Docker wegen bind mount standardmäßig Dateien, die root gehören. Ein schneller, „schmutziger“ Hack (wie chmod -R 777
) kann jedoch problematisch sein und dafür sorgen, dass durch die falschen Zugriffsrechte die Anwendung nicht mehr funktioniert.
Um das zu verhindern, bringen wir Docker dazu, den gleichen Benutzer wie auf unserem Host-System für seine Befehle zu benutzen. Dafür muss die Benutzer- und Gruppen-ID ausgelesen und an das Dockerfile durchgereicht werden. Hierfür haben wir die start.sh
angelegt:
#!/bin/bash # this is only relevant for unix hosts export USER_ID=$(id -u ${USER}) export GROUP_ID=$(id -g ${USER}) docker-compose up
Durchreichen der Benutzer- und Gruppen-ID an Docker
USER_ID und GROUP_ID geben wir über die docker-compose.yml
an den Container weiter …
[…] php: build: context: wallabagContainer/ args: USER_ID: ${USER_ID:-www-data} GROUP_ID: ${GROUP_ID:-www-data} […]
… und werden von dieser als build-args an das Dockerfile
weitergereicht. Dort werden sie – falls gesetzt – verwendet, um das Bauen der Applikation sowie späteres Verschieben in den Webserver-Ordner mit dem übergebenen Nutzer in der run.sh
auszuführen:
# Das hier muss ganz oben stehen, um als erstes angewendet zu werden FROM php:5.6-fpm […] ARG USER_ID ARG GROUP_ID # Zum Anlegen der Ordner benötigen wir noch Root-Rechte RUN mkdir /app && chown $USER_ID:$GROUP_ID /app RUN mkdir /.composer && chown $USER_ID:$GROUP_ID /.composer RUN chown $USER_ID:$GROUP_ID /app # Ab hier verwendet Docker den angegebenen Benutzer und die angegebene Gruppe für weitere Befehle USER $USER_ID:$GROUP_ID # Das Erstellen des app-Ordners wird nun weiter oben erledigt RUN cd /app && composer create-project wallabag/wallabag wallabag "2.0.0-beta.1" --no-dev # Und das hier muss ganz unten stehen CMD ["/run.sh"]
Unsere endgültige Verzeichnisstruktur sieht dann folgendermaßen aus:
. ├── docker-compose.yml ├── nginx.conf ├── start.sh └── wallabagContainer ├── Dockerfile ├── run.sh └── wallabag.sqlite 1 directory, 6 files
Willkommen in Docker, PHP-Applikation!
Das war es schon! Unter Unix einfach das Skript start.sh
(für erneute Starts reicht ein docker-compose up
), bei Windows docker-compose up
ausführen, und die App ist unter localhost:8080
erreichbar!
In unserem Repo modernizeYourPHP könnt ihr unser Projekt anschauen und selbst ausprobieren. Dort sind außerdem unsere Erfahrungen zu den eingangs erwähnten Tools und weiteren Dingen, die wir für PHP-Migrationen evaluiert haben, festgehalten.
Schreibe einen Kommentar