Best Practices für produktionsreife Dockerfiles für PHP-Anwendungen
James Walker
22. August 2023
0 Min. LesezeitDocker ist eine Containerisierungsplattform, mit der Sie Code, Abhängigkeiten und Laufzeitumgebung in eigenständige Einheiten bündeln können, die in verschiedenen Umgebungen identisch ausgeführt werden.
Die Containerisierung einer PHP-Anwendung mit Docker vereinfacht die Bereitstellung, da PHP-Laufzeitumgebung, Webserver, Quellcode und Composer-Abhängigkeiten in einem Container gebündelt werden. Der Einstieg in Docker ist einfach. Bevor Sie Docker sicher in der Produktion einsetzen können, sollten Sie jedoch einige Fallstricke vermeiden. Die Wahl eines geeigneten Basis-Images, einer passenden PHP-Laufzeitumgebung für Ihre Workload und die Absicherung Ihres Images sind entscheidend, um Probleme zu verhindern. In diesem Tutorial erfahren Sie, wie Sie ein zuverlässiges Dockerfile für PHP-Anwendungen schreiben.
Ein produktionsreifes Dockerfile für PHP erstellen
Wenn Sie diesen Artikel lesen, haben Sie wahrscheinlich bereits Ihre PHP-App geschrieben und möchten sie nun für die Bereitstellung in einen Docker-Container verpacken. Mit den folgenden zehn Best Practices maximieren Sie Leistung, Sicherheit, Zuverlässigkeit und einfache Entwicklung:
1. Ein spezifisches Basis-Image verwenden
Die Docker Community veröffentlicht ein offizielles Docker-Image für alle unterstützten PHP-Versionen. Es stehen verschiedene Image-Varianten zur Auswahl, die unterschiedliche Kombinationen aus Basisbetriebssystemen, Sprachversionen und Webserver-Integrationen (wie Apache oder FastCGI Process Manager (FPM)) abdecken.
Es ist wichtig, das Image auszuwählen, das am besten zu Ihren Anforderungen passt. Vermeiden Sie generische Tags wie php:latest, da diese unerwünschte, möglicherweise inkompatible Codeänderungen und unnötige Bibliotheken enthalten können, die nur für ein vollständiges Betriebssystem erforderlich sind. Das Image php:apache enthält beispielsweise derzeit PHP 8.2, wechselt aber mit der Veröffentlichung dieser Version sofort zu PHP 9.0. Wenn Sie php:8.2-apache auswählen, wird Ihr Image immer PHP 8.2 ausführen – auch wenn diese Version nicht mehr der aktuelle Release-Kanal ist:
Dasselbe Prinzip gilt für alle anderen Images, von denen Sie abhängig sind, etwa composer für die Installation von Abhängigkeiten. Wählen Sie den Image-Tag passend zur Composer-Hauptversion, die Sie auf Ihrem Rechner verwenden – composer:2 statt composer:latest.
2. Umgebungsvariablen für die Konfiguration verwenden
Docker-Container sind zustandslos. Am besten konfigurieren Sie Ihre Anwendung mithilfe von Umgebungsvariablen, die Sie beim Start des Containers festlegen. Das ist besser, als sich auf eine Konfigurationsdatei zu verlassen, für die Sie dauerhaft Speicherplatz im Container einrichten müssten – etwa mit einem Docker-Volume.
Umgebungsvariablen legen Sie beim Starten eines Containers mit dem Flag -e fest, wenn Sie docker run verwenden:
Ihr PHP-Code kann den Wert der Variablen mithilfe der Funktion getenv() abrufen:
Diese Variablen werden zur Laufzeit für einzelne Container festgelegt. Manchmal möchten Sie vielleicht eine Variable in Ihrem Dockerfile definieren, damit sie Containern automatisch zur Verfügung steht. Verwenden Sie dazu die Anweisung ENV:
Umgebungsvariablen lassen sich auch während des Builds festlegen und mithilfe des Schlüsselworts ARG in anderen Anweisungen Ihres Dockerfiles referenzieren:
Um den Wert des Arguments festzulegen, verwenden Sie beim Ausführen von docker build das Flag --build-arg:
3. PHP FPM oder Nginx/Apache abwägen
FPM ist eine alternative PHP-FastCGI-Implementierung zur Integration von PHP in Webserver wie Apache und Nginx. Als Alternative zu FPM können Sie den Apache-Webserver mit mod_php verwenden. Im Vergleich zur Apache-Alternative bietet FPM ein fortschrittliches, nicht blockierendes Prozessmanagement, die Möglichkeit, verschiedene Pools von Worker-Prozessen anzulegen, und eine bessere Fehlerbehandlung. Bei großen Apps sorgt FPM oft für einen deutlichen Leistungsschub.
Das Docker-Basis-Image für PHP ist sowohl in FPM- als auch in mod_php-Varianten verfügbar:
php:8.2-fpm: Dieses Image und ähnliche Tags stellen eine FPM-Verbindung bereit.php:8.2-apache: Diese Images enthalten eine Apache-Installation und verwendenmod_phpzur Verarbeitung von PHP-Anfragen.
Wenn Sie Apache verwenden und mit mod_php zufrieden sind, wählen Sie eines der :apache-Basis-Images. Sie können Ihren PHP-Code in das Verzeichnis /var/www/html kopieren und sofort ausführen.
Für FPM benötigen Sie einen separaten Container, auf dem ein Server wie Nginx läuft. Konfigurieren Sie ihn als Reverse-Proxy, der PHP-Anfragen an den Port weiterleitet, den der FPM-Prozess in Ihrem PHP-Container bereitstellt. Die Standardportnummer ist 9000.
Verwenden Sie FPM, wenn Sie maximale Leistung wünschen und es Ihnen nichts ausmacht, zwei Container zu verwalten: einen FPM-Container und einen vorgeschalteten Webserver. Apache-basierte Images eignen sich hervorragend für einen schnellen Einstieg, ohne dass Sie zusätzliche Container konfigurieren oder die FPM-Worker-Pool-Einstellungen feinabstimmen müssen.
4. Unnötige Debug-Protokolle und Fehlerberichte in der Produktion deaktivieren
Produktionsanwendungen sollten keine Informationen öffentlich preisgeben, die für Angreifer nützlich sein könnten. Deaktivieren Sie die PHP-Einstellungen display_errors und display_startup_errors, damit unbehandelte Ausnahmen nicht auf Ihrer Website angezeigt werden.
Ändern Sie dazu die Datei php.ini in Ihrem Container.
Erstellen Sie zunächst eine geänderte Datei:
Passen Sie anschließend Ihr Dockerfile so an, dass php.ini an den richtigen Ort im Dateisystem des Containers kopiert wird:
Für den Produktionseinsatz müssen Sie möglicherweise die PHP-Fehlerberichtsstufe ändern. Fehler niedriger Stufen wie Hinweise und Deprecation-Warnungen können Protokolle schnell mit nicht umsetzbaren Informationen füllen. Sie können diesen Wert beim Start Ihres Skripts dynamisch anhand einer Umgebungsvariablen festlegen:
5. PHP-Container als Nicht-Root-Benutzer ausführen
Docker führt Container standardmäßig als root aus. Der Prozess im Container hat dieselben Berechtigungen wie root auf Ihrem Host. Wird der Container kompromittiert und seine Isolation überwunden, können schädliche Prozesse beliebige Befehle auf Ihrem Host ausführen.
Wenn Sie Ihren Container als dedizierten Nicht-Root-Benutzer ausführen, lassen sich diese Risiken mindern. Verwenden Sie die Anweisung USER in Ihrem Dockerfile, um ab diesem Punkt zu einem anderen Benutzer zu wechseln:
6. Integritätsprüfungen für PHP-Container einrichten
Docker verfügt über einen integrierten Mechanismus für Integritätsprüfungen, der erkennt, wenn Container ausfallen. Sie können Docker so konfigurieren, dass alle dreißig Sekunden automatisch ein Prüfungsbefehl ausgeführt wird. Wird der Befehl mit dem Code 0 beendet, gilt der Container als intakt. Ein Beendigungsstatus von 1 weist hingegen auf einen Anwendungsausfall hin.
Mit der Anweisung HEALTHCHECK in Ihrem Dockerfile können Sie Integritätsprüfungen für Ihre Anwendungen einrichten. Bei einem PHP-Service können Sie mit curl eine Netzwerkanfrage an einen Endpunkt Ihrer App senden. Wird curl mit 0 beendet, bedeutet das, dass eine Antwort mit einem HTTP-Statuscode aus dem Erfolgsbereich 2xx empfangen wurde:
Der Integritätsstatus Ihrer Container wird in der Ausgabe des Befehls docker ps angezeigt:
Nach einer fehlgeschlagenen Integritätsprüfung wird der Status des Containers als unhealthy angezeigt. Container-Orchestratoren wie Kubernetes nutzen dieses Signal, um ausgefallene Container automatisch neu zu starten oder zu ersetzen.
7. Anzahl der verwendeten Ports begrenzen
Vermeiden Sie es, unnötige Ports Ihrer Container freizugeben. Nur Ports, die externe Prozesse erreichen müssen, sollten verfügbar sein. Über diese Ports verbinden sich andere Container und öffentliche Benutzer.
Bei PHP-Anwendungen ist dies oft Port 80, wenn Sie ein Image mit integriertem Webserver wie php:8.2-apache verwenden. FPM-basierte Images sollten stattdessen Port 9000 bereitstellen, damit sich ein separater Webserver-Container verbinden kann. Der FPM-Port sollte nicht öffentlich zugänglich sein.
Achten Sie außerdem darauf, welche Informationen Sie über Umgebungsvariablen preisgeben. Entfernen Sie nicht verwendete Variablen, um das Risiko einer Offenlegung zu senken. Tools wie docker inspect und jede Schadsoftware im Container können stets auf die vollständige Liste der von Ihnen festgelegten Variablen zugreifen.
8. Abhängigkeiten regelmäßig aktualisieren
Alle Komponenten Ihres Stacks sollten regelmäßig aktualisiert werden, damit neue Fehlerkorrekturen und Sicherheitsupdates angewendet werden. Dazu gehören:
Aktualisierungen des Basisbetriebssystems in Ihrem Docker-Image.
Aktualisierungen der PHP-Version und der von Ihnen verwendeten Erweiterungen.
Composer-Pakete, die Sie als Abhängigkeiten in Ihrem Projekt installiert haben.
Das ist entscheidend, damit Sie vor neuen Sicherheitslücken geschützt bleiben. Erstellen Sie Ihr Image mit docker build --pull neu, um bei jedem Build Updates zu erhalten. Dadurch wird die neueste Version Ihres Basis-Images abgerufen, einschließlich aktualisierter Betriebssystempakete und PHP-Versionen.
Führen Sie außerdem regelmäßig composer update in Ihrem Projekt aus, um die neuesten Patches für Ihre Abhängigkeiten einzuspielen. Mit Tools wie Snyk finden Sie veraltete oder anfällige Abhängigkeiten und erhalten Vorschläge für ein Update auf eine Version mit Fehlerbehebung. Nachdem Sie Ihre Pakete mit Composer aktualisiert haben, erstellen Sie Ihr Docker-Image neu, damit in Produktionsbereitstellungen die neuen Versionen zum Einsatz kommen.
9. Container-Funktionen auf das erforderliche Minimum beschränken
Linux-Kernel-Funktionen beschränken potenziell sensible Aktionen, die Prozesse ausführen können. Docker führt Container bereits mit einem nicht privilegierten Satz von Funktionen aus, doch selbst dieser ist für typische PHP-Workloads noch zu umfangreich. Standardmäßig dürfen Container-Prozesse beispielsweise Dateiberechtigungen ändern, Prozesse beenden und Benutzer-IDs manipulieren.
Mit den Flags --cap-add und --cap-drop für docker run können Sie Container-Funktionen hinzufügen oder entfernen. Am sichersten ist es, mit --cap-drop=all alle Funktionen zu entfernen und anschließend nur die spezifischen Funktionen hinzuzufügen, die Ihre App benötigt.
Das folgende Beispiel stattet den Container nur mit der Funktion CHOWN aus. Damit kann der Container die Besitzverhältnisse von Dateien und Ordnern ändern, während alle anderen privilegierten Aktionen unterbunden werden:
10. Mehrstufige Builds für die Trennung von Build und Laufzeit verwenden
Mehrstufige Builds erleichtern das Schreiben und Warten komplexerer Build-Pipelines. Sie ermöglichen den Einsatz mehrerer Basis-Images im Build-Prozess und sorgen zugleich für ein kleines, präzises Ergebnis.
Das mehrstufige Build-Muster eignet sich ideal für PHP-Dockerfiles, da Sie Composer-Abhängigkeiten normalerweise während des Builds installieren möchten. Die offiziellen PHP-Docker-Images enthalten Composer nicht – das Projekt veröffentlicht stattdessen eigene Images. Mit einem mehrstufigen Build können Sie die Composer-Binärdatei bequem in Ihrem Dockerfile referenzieren:
Das Image composer:2 wird nach Abschluss des Builds verworfen. So bleiben Build- und Laufzeitumgebung sauber getrennt, ohne dass sich die Größe Ihres endgültigen Images erhöht.
Sicherheitslücken mit Snyk erkennen und beheben
Docker kann die Sicherheit verbessern, indem es Ihre PHP-Anwendungen von ihren Host-Umgebungen isoliert. Container-Images enthalten jedoch häufig Sicherheitslücken, die auf veraltete Betriebssystempakete und Composer-Abhängigkeiten zurückzuführen sind:

Bevor Sie ein Image in der Produktion bereitstellen, sollten Sie es unbedingt auf Sicherheitslücken untersuchen. Mit Snyk können Sie Ihr PHP-Docker-Image scannen sowie Sicherheitslücken erkennen und beheben. Die Snyk Vulnerability Database enthält Einträge zu allen gängigen Betriebssystemen und Abhängigkeiten, einschließlich PHP-Paketen, die auf Packagist veröffentlicht wurden.
Um mit Snyk Sicherheitslücken zu erkennen, erstellen Sie eine einfache PHP-Datei und speichern Sie sie unter index.php:
Schreiben Sie anschließend ein Dockerfile für Ihre Anwendung:
Erstellen Sie das Image mit Docker:
Rufen Sie anschließend die Snyk-Website auf und klicken Sie auf Kostenlos starten, um Ihr Konto zu erstellen. Folgen Sie den Schritten zur Einrichtung und installieren Sie dann die Snyk CLI. Außerdem benötigen Sie Node.js und npm:
Führen Sie als Nächstes den Befehl snyk auth aus. Daraufhin wird ein neuer Browser-Tab geöffnet, in dem Sie sich bei Snyk anmelden und die CLI authentifizieren können. Wechseln Sie anschließend zurück zu Ihrem Terminal.
Führen Sie den folgenden Befehl aus, um Ihr Image zu scannen:
Der Scan kann einige Augenblicke dauern. Anschließend werden die Ergebnisse in Ihrem Terminal angezeigt. Sie sehen eine Liste aller erkannten Sicherheitslücken – einschließlich des betroffenen Pakets, der anfälligen Versionen und eines Links mit weiteren Informationen:

Die Zusammenfassung am Ende zeigt die Gesamtzahl der gefundenen Probleme und schlägt alternative Basis-Images mit weniger Sicherheitslücken vor.
Erkannte Sicherheitslücken beheben
Wenn Sicherheitslücken gefunden werden, sollten Sie jede einzelne bewerten und entscheiden, ob Sie Maßnahmen ergreifen müssen. Nicht jede Sicherheitslücke stellt zwangsläufig ein Problem für Ihren Anwendungsfall dar. Konzentrieren Sie sich zunächst auf Probleme mit kritischem, hohem und mittlerem Schweregrad.
Am effektivsten ist es oft, Ihr Image mit einer aktualisierten Basis neu zu erstellen. Beliebte Community-Images wie php:8.2-fpm werden regelmäßig mit neuen Versionen veröffentlicht, die Korrekturen für Sicherheitslücken enthalten. Scannen Sie Ihr Image nach dem Neuerstellen erneut, um die Auswirkungen auf Ihre Bewertung zu prüfen.
Im Bericht können zahlreiche Sicherheitslücken in Betriebssystemkomponenten auf niedriger Ebene aufgeführt sein. Verwenden Sie ein kleineres, minimales Basis-Image, um Ihre Angriffsfläche insgesamt zu verringern. Alpine Linux-Varianten wie php:fpm-alpine enthalten deutlich weniger Pakete und damit oft auch weniger Sicherheitslücken.
Zum Zeitpunkt der Erstellung dieses Artikels enthält das Image php:8.2-fpm beispielsweise 98 erkannte Sicherheitslücken:

Die Variante php:8.2-fpm-alpine weist nur zehn auf, da sie deutlich weniger Betriebssystempakete enthält:

Manche Sicherheitslücken lassen sich in Ihrem Projekt möglicherweise nicht beheben. Probleme in Betriebssystempaketen haben beispielsweise unter Umständen keine Auswirkungen auf Ihre Workload, oder es ist kein Patch verfügbar. Sie können eine Sicherheitslücke in einem Snyk-Bericht ignorieren, indem Sie ihre ID an den Befehl snyk ignore übergeben. Die ID finden Sie am Ende der URL der Sicherheitslücke, zum Beispiel SNYK-ALPINE317-CURL-3320725 in https://security.snyk.io/vuln/SNYK-ALPINE317-CURL-3320725:
Denken Sie schließlich daran, dass Containersicherheit aus mehreren Komponenten besteht. Sie benötigen ein sicheres Basis-Image, eine aktuelle und unterstützte PHP-Runtime-Version sowie gepatchte Versionen Ihrer Abhängigkeiten und Toolchain-Komponenten sowie Ihres Anwendungscodes. Wenn Sie Ihr Image regelmäßig auf Sicherheitslücken scannen, behalten Sie den Überblick über Ihren Sicherheitsstatus.
Zusammenfassung
Nach der Lektüre dieses Artikels sollten Sie über ein produktionsreifes Docker-Image verfügen, mit dem Sie Ihre PHP-Anwendung ausführen können. In diesem Leitfaden haben Sie wichtige Best Practices für das Schreiben Ihrer Dockerfile und die Verwendung der erstellten Images kennengelernt. Dazu gehören die Auswahl eines bestimmten Basis-Images, die Verwendung mehrstufiger Builds zum Installieren von Abhängigkeiten und das Scannen auf Sicherheitslücken, um sicherzustellen, dass Ihre Workloads für den Produktionseinsatz geeignet sind.
Weitere Empfehlungen zu Docker-Images und PHP finden Sie in der Docker-Dokumentation und in den anderen Artikeln im Snyk-Blog.
Snyk ist eine entwicklerfreundliche Sicherheitsplattform für alle, die für die Sicherheit von Code verantwortlich sind. Mit Snyk können Sie Ihre Docker-Images auf Sicherheitslücken in Abhängigkeiten, Betriebssystempaketen und PHP-Code scannen. Snyk bietet außerdem ein IDE-Plugin, das per statischer Analyse Sicherheitslücken erkennt, sobald sie entstehen. Melden Sie sich jetzt an, um Sicherheitslücken in Ihren PHP-Container-Images automatisch zu finden und zu beheben.
Container-Sicherheit mit Fokus auf Entwickler
Snyk findet und behebt automatisch Schwachstellen in Container-Images und Kubernetes-Workloads.
