Der Apache-Webserver ist aufgrund seiner Vielseitigkeit und Flexibilität bis heute beliebt. Häufig wird er zusammen mit der Skriptsprache PHP eingesetzt. Apache und PHP stellen offizielle Images bereit, die den einfachen Betrieb auf Basis von Docker und Docker-Compose ermöglichen. Dieser Artikel zeigt eine minimalistische Konfiguration.
Beginnen wir mit der Struktur unseres Arbeitsverzeichnisses:
.
├── docker-compose.yml
├── htdocs
│ └── index.php
├── htdpcs
└── httpd.conf
Der Ordner htdocs wird später vom Webserver ausgeliefert. Die index.php Datei liefert beispielhaft phpinfo(); aus. So können wir später die korrekte Konfiguration von PHP testen.
Als Basis wird die docker-compose.yml Datei erstellt:
version: "2.4"
services:
apache:
image: httpd:2.4
volumes:
- ./httpd.conf:/usr/local/apache2/conf/httpd.conf
- ./htdocs:/usr/local/apache2/htdocs
ports:
- 80:80
php:
image: php:7.3-fpm
volumes_from:
- apache
Wir definieren also einen Apache-Container, der sowohl die Konfigurationsdatei, als auch den htdocs-Ordner als Volume gemountet erhält. Durch die Portfreigabe ist der Webserver auf dem Host lokal über Port 80 erreichbar. Abgesehen von kleineren Umgebungen wird man produktiv dafür eher einen Reverse-Proxy wie traefik einsetzen.
Der PHP-Container erhält diese Volumes auch, damit er die PHP-Skripte ausführen kann. Zum Einsatz kommt das jeweils aktuellste PHP 7.3 Image. So erhält der Container die aktuellsten Sicherheitsaktualisierungen. Bei darf kann hier auch eine ältere Version genutzt werden. php:7.2-fpm würde beispielsweise die Vorversion ausführen.
Apache2 Konfiguration
Die Datei httpd.conf füllen wir mit folgender Konfiguration:
Listen 80
LogLevel info
ErrorLog /proc/self/fd/2
User daemon
Group daemon
ServerRoot "/usr/local/apache2"
ServerName localhost
DirectoryIndex index.php index.html
# https://httpd.apache.org/docs/2.4/mod/
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule dir_module modules/mod_dir.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
LoadModule filter_module modules/mod_filter.so
LoadModule deflate_module modules/mod_deflate.so
CustomLog /proc/self/fd/1 common
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
TypesConfig conf/mime.types
AddType application/x-compress .Z
AddType application/x-gzip .gz .tgz
SetOutputFilter DEFLATE
AddOutputFilterByType DEFLATE text/html text/plain text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/x-javascript application/javascript application/ecmascript
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/xml
<Directory />
AllowOverride none
Require all denied
</Directory>
<Files ".ht*">
Require all denied
</Files>
#ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://php:9000/usr/local/apache2/htdocs/$1
<VirtualHost *:80>
DocumentRoot "/usr/local/apache2/htdocs"
<Directory "/usr/local/apache2/htdocs">
Options Indexes FollowSymlinks
AllowOverride None
Require all granted
<FilesMatch "\.php$">
SetHandler "proxy:fcgi://php:9000/"
</FilesMatch>
</Directory>
</VirtualHost>
Hierbei handelt es sich um eine minimalistische Basiskonfiguration für Apache2. Es werden nur die Kernmodule geladen. Sie aktiviert das Logging auf dem stdout Kanal. So lassen sich die Logs auf der Konsole bzw. mit docker logs im Detached-Modus abrufen. Darüber hinaus wird das standardmäßige MIME-Mapping von Apache aktiviert sowie die GZIP-Kompromierung. Und das wichtigste für diesen Stack: Weiterreichen von PHP-Dateien an PFP-FPM mit dem FastCGI-Modul.
Testlauf
Final wird htdocs/index.php angelegt und gefüllt:
<?php phpinfo();
Nun sind alle erforderlichen Dateien vorhanden. Die Container können gestartet werden:
docker-compose up
Wahlweise kann man auch -d anhängen, damit die Container in Hintergrund gestartet werden. Gerade zum testen finde ich attached besser, hier sind alle Ausgaben auf dem Terminal sichtbar.

Durch den Aufruf von http://localhost im Browser sollte die PHP Infoseite sichtbar werden:

Warum nicht nur die Standard httpd.conf anpassen?
Teils wird in Beispielen keine eigene Apache-Konfigurationsdatei namens httpd.conf erstellt. Sondern ein Docker-Image, worin dann Ersetzungen auf der Standard-Konfigurationsdatei durchgeführt werden. Beispielsweise lässt sich mit sed die Raute vor einer LoadModule Direktive entfernen, um ein Modul zu laden. Oder man fügt einfach eigene Zeilen hinzu. Grundsätzlich funktioniert das, doch ich empfehle dieses Vorgehen aus zwei Gründen nicht:
Potenzielle Fehlerquellen
Je nach Einsatzzweck können manche Standardwerte einem Probleme bereiten. Diese muss man ermitteln und entsprechend anpassen oder entfernen. Danach verlässt man sich auf die verbliebenen Standardwerte. Werden diese irgendwann mal geändert, funktioniert die daraus entstehende Konfiguration u.u. nicht mehr wie erwartet.
Übersicht und Wartbarkeit
Selten bleibt es bei 1 oder 2 Änderungen. Je mehr angepasst werden muss, um so unübersichtlicher wird das Dockerfile. Die Komplexität steigt dadurch sowohl für die Fehlerauche, als auch spätere Erweiterungen.
Performance und Sicherheit
Die Standard-Konfiguration ist darauf ausgelegt, in vielen Fällen möglichst einfach zu funktionieren. Das beugt Frust bei Einsteigern vor, aber ist nicht optimal für die Leistung. Beispielsweise sind standardmäßig viele Module aktiviert, die oft gar nicht alle benötigt werden. Der Apache lädt sie aber trotzdem bei jeder Anfrage. Dies entspricht auch nicht den Best Practices hinsichtlich Sicherheit.
Schlussfolgerung: Eigene Konfiguration
Aus diesen Gründen halte ich eine eigene, explizite Konfiguration für sinnvoller. Darin aktiviert man nur das, was benötigt wird. Unabhängig von Docker hat sich dieses Vorgehen bereits in mehreren Szenarien bewährt. Der Mehraufwand ist überschaubar. Wird mit mehreren Projekten gearbeitet, kann eine Basis-Konfiguration wie aus diesem Artikel als Grundlage genommen werden. Diese passt man dann an und lädt beispielsweise zusätzliche Module, welche im jeweiligen Projekt benötigt werden.