Dockerfile: die Images
Ein Dockerfile, zwei Images: PHP-FPM mit TYPO3 und nginx mit denselben Dateien. Jede Anweisung erklärt.
Überblick: Multi-Stage-Build
Das Dockerfile enthält zwei Stages. Jede Stage beginnt mit einem eigenen FROM und ergibt ein eigenes Image:
app: PHP-FPM mit allen Systempaketen, PHP-Erweiterungen und der per Composer installierten TYPO3-Installation. Daraus laufen die Servicesphpundscheduler.web: nginx mit einer Kopie der Dateien ausapp. Daraus läuft der Serviceweb.
Welche Stage gebaut wird, legt docker-compose.yml mit build.target fest. Weil web seine Dateien aus app kopiert, haben beide Images garantiert denselben Stand. nginx kann statische Dateien wie CSS, JavaScript und Bilder direkt ausliefern, ohne PHP zu bemühen.
Syntax und Build-Argumente
# syntax=docker/dockerfile:1
ARG PHP_VERSION=8.4
ARG NGINX_VERSION=1.30Die erste Zeile ist eine Parser-Direktive: BuildKit verwendet die aktuelle Dockerfile-Syntax der Version 1. Die beiden ARG-Zeilen stehen vor dem ersten FROM und dürfen deshalb in den FROM-Zeilen verwendet werden. Sie legen fest, welche PHP- und nginx-Version als Basis dient.
Für ein Update genügt es, die Werte hier zu ändern und neu zu bauen. Alternativ setzt man sie in docker-compose.yml unter build.args, ohne das Dockerfile anzufassen.
Basis-Image und Umgebung
FROM php:${PHP_VERSION}-fpm AS app
ENV TYPO3_CONTEXT=Production \
COMPOSER_ALLOW_SUPERUSER=1 \
COMPOSER_NO_INTERACTION=1Basis ist das offizielle PHP-Image in der FPM-Variante auf Debian. AS app gibt der Stage ihren Namen.
TYPO3_CONTEXT=Productionist der Standardkontext.docker-compose.ymlüberschreibt ihn mit dem Wert aus der.env, zum BeispielDevelopmentfür ausführliche Fehlermeldungen.COMPOSER_ALLOW_SUPERUSER=1unterdrückt die Warnung, dass Composer während des Builds als root läuft.COMPOSER_NO_INTERACTION=1verhindert Rückfragen, die einen Build blockieren würden.
Systempakete
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
graphicsmagick \
ghostscript \
git \
locales \
unzip \
&& rm -rf /var/lib/apt/lists/*| Paket | Wofür |
|---|---|
graphicsmagick | Bildverarbeitung: Skalieren, Zuschneiden, Vorschaubilder. additional.php stellt TYPO3 darauf ein. |
ghostscript | Vorschaubilder von PDF-Dateien |
git, unzip | Composer lädt Pakete als Git-Klon oder ZIP-Archiv |
locales | Systemsprachen für Datums- und Zahlenformate (nächster Abschnitt) |
Alles steht in einer einzigen RUN-Anweisung. So entsteht nur eine Image-Schicht, und die Paketlisten unter /var/lib/apt/lists werden in derselben Schicht wieder gelöscht. --no-install-recommends spart empfohlene, aber nicht benötigte Pakete. Beides hält das Image klein.
Systemsprachen (Locales)
RUN sed -i -E 's/^# *(en_US\.UTF-8|de_DE\.UTF-8) UTF-8/\1 UTF-8/' /etc/locale.gen \
&& locale-genDie Site-Sprachen verwenden die Locales de_DE.UTF-8 und en_US.UTF-8. TYPO3 setzt sie per setlocale(), etwa für Monatsnamen und Zahlenformate. Im Debian-Image sind sie nicht erzeugt. Der sed-Befehl entfernt das Kommentarzeichen vor den beiden Einträgen in /etc/locale.gen, locale-gen erzeugt sie. Ohne diesen Schritt meldet TYPO3 im Log „Locale … not found“.
Für eine weitere Sprache ergänzt man sie im regulären Ausdruck, zum Beispiel fr_FR\.UTF-8.
Zusätzliche CA-Zertifikate
COPY config/certs/ /usr/local/share/ca-certificates/
RUN update-ca-certificatesTYPO3 spricht serverseitig mit Keycloak: Token-Endpunkt, Userinfo und Signaturschlüssel. Das Zertifikat von id.dev.sonia.de stammt von der Ostfalia RZ Root CA, die Debian nicht kennt. update-ca-certificates nimmt alle *.crt-Dateien aus dem Ordner in den Zertifikatsspeicher des Systems auf. PHP (cURL, OpenSSL) vertraut ihnen danach.
Weitere interne CAs legt man einfach als .crt-Datei in config/certs/ ab und baut neu.
PHP-Erweiterungen
COPY --from=mlocati/php-extension-installer:2 /usr/bin/install-php-extensions /usr/local/bin/
RUN install-php-extensions \
apcu \
exif \
gd \
intl \
mysqli \
opcache \
pdo_mysql \
zipCOPY --from holt nur das Hilfsprogramm install-php-extensions aus einem fremden Image. Es installiert die nötigen Bibliotheken, kompiliert die Erweiterungen und räumt die Build-Werkzeuge wieder ab.
| Erweiterung | Wofür |
|---|---|
apcu | schneller Cache im Arbeitsspeicher |
exif | Metadaten aus Fotos, etwa Ausrichtung und Aufnahmedatum |
gd | Bildfunktionen in PHP, ergänzt GraphicsMagick |
intl | Internationalisierung, von TYPO3 vorausgesetzt |
mysqli, pdo_mysql | Datenbankzugriff auf MariaDB. Die Appliance nutzt den Treiber mysqli. |
opcache | hält kompilierten PHP-Code im Speicher, deutlich schneller |
zip | ZIP-Archive, etwa für Sprachpakete und Extension-Uploads |
Standarderweiterungen wie mbstring, xml oder curl bringt das PHP-Image bereits mit.
Composer und PHP-Konfiguration
COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
COPY config/php/typo3.ini /usr/local/etc/php/conf.d/zz-typo3.ini
COPY config/php/fpm-pool.conf /usr/local/etc/php-fpm.d/zz-typo3.confComposer kommt ebenfalls per COPY --from aus dem offiziellen Composer-Image, Version 2.
typo3.inisetzt die PHP-Werte nach den TYPO3-Systemanforderungen: Speicherlimit, Ausführungszeit, Upload-Größe, Zeitzone, OPcache und APCu.fpm-pool.confpasst den FPM-Pool an. Entscheidend istclear_env = no: Sonst sähe PHP die Umgebungsvariablen aus Compose nicht, undadditional.phpbliebe ohne Konfiguration. Außerdem stellt sie die Prozessanzahl ein und richtet den Ping-Endpunkt für den Healthcheck ein.
Das Präfix zz- sorgt dafür, dass die Dateien als letzte geladen werden und die Standardwerte überschreiben.
TYPO3 per Composer installieren
WORKDIR /var/www/html
# Abhängigkeiten zuerst installieren (Layer-Caching)
COPY composer.json composer.lock* ./
COPY packages/ packages/
RUN composer install --no-dev --optimize-autoloader --no-progress \
&& composer clear-cache \
&& mkdir -p config/system config/sites var public/fileadmin public/typo3temp \
&& chown -R www-data:www-data /var/www/htmlHier entsteht die eigentliche TYPO3-Installation. Ins Image kommen nur composer.json, composer.lock und packages/. Den Rest erzeugt Composer, darunter vendor/ und den Webroot public/ mit index.php und den öffentlichen Extension-Dateien unter public/_assets.
composer.lock*mit Stern: Die Datei ist optional. Ist sie vorhanden, installiert Composer exakt die festgeschriebenen Versionen.packages/muss vorcomposer installkopiert werden, weil die eigenen Extensions als lokale Pakete eingebunden sind.--no-devlässt Entwicklungswerkzeuge weg,--optimize-autoloaderbeschleunigt das Laden der Klassen,clear-cacheentfernt den Download-Cache aus dem Image.mkdirlegt die Ordner an, auf die später die Volumes gelegt werden.chowngibt sie dem Benutzerwww-data, unter dem PHP-FPM arbeitet.
Layer-Caching: Docker baut eine Schicht nur neu, wenn sich ihre Eingaben geändert haben. Solange composer.json, composer.lock und packages/ gleich bleiben, übernimmt Docker diesen langwierigen Schritt aus dem Cache.
Startskripte, Entrypoint und Kommando
COPY config/typo3/additional.php /usr/local/share/typo3/additional.php
COPY config/typo3/entrypoint.sh /usr/local/bin/typo3-entrypoint
COPY config/typo3/healthcheck.sh /usr/local/bin/typo3-healthcheck
RUN chmod +x /usr/local/bin/typo3-entrypoint /usr/local/bin/typo3-healthcheck
EXPOSE 9000
ENTRYPOINT ["typo3-entrypoint"]
CMD ["php-fpm"]additional.phpliest Datenbank, Hostnamen, Mail und OIDC aus Umgebungsvariablen. Sie liegt zunächst außerhalb vonconfig/, denn dort liegt zur Laufzeit das Volumetypo3-configund würde die Datei verdecken. Der Entrypoint kopiert sie bei jedem Start nachconfig/system/. So gilt immer die Fassung aus dem Image.entrypoint.shwartet auf die Datenbank. Beim ersten Start führt estypo3 setupaus und richtet Site, Sprachen und Inhalte ein. Bei jedem Start aktualisiert es Datenbankschema, Backend-Gruppen und Sprachpaket und leert die Caches.healthcheck.shmeldet „gesund“, sobald der Entrypoint fertig ist und PHP-FPM auf Port 9000 antwortet.EXPOSE 9000dokumentiert den FPM-Port. Veröffentlicht wird er nicht, nginx erreicht ihn über das interne Docker-Netz.ENTRYPOINTundCMDwirken zusammen: Docker startettypo3-entrypoint php-fpm. Das Skript endet mitexec "$@", dadurch ersetzt PHP-FPM den Skriptprozess und empfängt die Stopp-Signale von Docker direkt.
Stage web: nginx
FROM nginx:${NGINX_VERSION}-alpine AS web
COPY config/nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=app /var/www/html /var/www/htmlDie zweite Stage startet von einem schlanken nginx-Image auf Alpine Linux. Sie übernimmt den TYPO3-Server-Block und kopiert mit COPY --from=app das komplette Verzeichnis /var/www/html aus der ersten Stage.
nginx braucht diese Dateien aus zwei Gründen. Erstens liefert es public/_assets und andere statische Dateien selbst aus. Zweitens prüft der Server-Block vor jeder Weitergabe an PHP, ob die angefragte PHP-Datei existiert. PHP selbst ist in diesem Image nicht enthalten.
Uploads (fileadmin) und generierte Dateien (typo3temp) entstehen erst zur Laufzeit. Sie kommen deshalb nicht aus dem Image, sondern als Volumes aus docker-compose.yml.
Was nicht ins Image kommt
.git
.env
.env.*
!.env.example
vendor/
public/
var/
*.md
docker-compose*.yml
backups/
BACKUP.sh
RESTORE.sh
CREATE_SHELL_ARCHIVE_BACKUP.shDie Datei .dockerignore schließt Dateien vom Build-Kontext aus. Docker überträgt sie gar nicht erst an den Build.
- Sicherheit:
.envmit Passwörtern und Client-Secret landet nie in einem Image. Die Werte kommen zur Laufzeit über Compose. - Sauberkeit: Ein lokal vorhandenes
vendor/,public/odervar/würde den Composer-Lauf im Image verfälschen. - Tempo: Backups, Dokumentation und Skripte werden im Image nicht gebraucht und machen den Build-Kontext nur größer.