Konfigurationsdateien

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 Services php und scheduler.
  • web: nginx mit einer Kopie der Dateien aus app. Daraus läuft der Service web.

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.30

Die 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=1

Basis ist das offizielle PHP-Image in der FPM-Variante auf Debian. AS app gibt der Stage ihren Namen.

  • TYPO3_CONTEXT=Production ist der Standardkontext. docker-compose.yml überschreibt ihn mit dem Wert aus der .env, zum Beispiel Development für ausführliche Fehlermeldungen.
  • COMPOSER_ALLOW_SUPERUSER=1 unterdrückt die Warnung, dass Composer während des Builds als root läuft.
  • COMPOSER_NO_INTERACTION=1 verhindert 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/*
PaketWofür
graphicsmagickBildverarbeitung: Skalieren, Zuschneiden, Vorschaubilder. additional.php stellt TYPO3 darauf ein.
ghostscriptVorschaubilder von PDF-Dateien
git, unzipComposer lädt Pakete als Git-Klon oder ZIP-Archiv
localesSystemsprachen 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-gen

Die 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-certificates

TYPO3 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 \
        zip

COPY --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.

ErweiterungWofür
apcuschneller Cache im Arbeitsspeicher
exifMetadaten aus Fotos, etwa Ausrichtung und Aufnahmedatum
gdBildfunktionen in PHP, ergänzt GraphicsMagick
intlInternationalisierung, von TYPO3 vorausgesetzt
mysqli, pdo_mysqlDatenbankzugriff auf MariaDB. Die Appliance nutzt den Treiber mysqli.
opcachehält kompilierten PHP-Code im Speicher, deutlich schneller
zipZIP-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.conf

Composer kommt ebenfalls per COPY --from aus dem offiziellen Composer-Image, Version 2.

  • typo3.ini setzt die PHP-Werte nach den TYPO3-Systemanforderungen: Speicherlimit, Ausführungszeit, Upload-Größe, Zeitzone, OPcache und APCu.
  • fpm-pool.conf passt den FPM-Pool an. Entscheidend ist clear_env = no: Sonst sähe PHP die Umgebungsvariablen aus Compose nicht, und additional.php bliebe 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/html

Hier 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 vor composer install kopiert werden, weil die eigenen Extensions als lokale Pakete eingebunden sind.
  • --no-dev lässt Entwicklungswerkzeuge weg, --optimize-autoloader beschleunigt das Laden der Klassen, clear-cache entfernt den Download-Cache aus dem Image.
  • mkdir legt die Ordner an, auf die später die Volumes gelegt werden. chown gibt sie dem Benutzer www-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.php liest Datenbank, Hostnamen, Mail und OIDC aus Umgebungsvariablen. Sie liegt zunächst außerhalb von config/, denn dort liegt zur Laufzeit das Volume typo3-config und würde die Datei verdecken. Der Entrypoint kopiert sie bei jedem Start nach config/system/. So gilt immer die Fassung aus dem Image.
  • entrypoint.sh wartet auf die Datenbank. Beim ersten Start führt es typo3 setup aus und richtet Site, Sprachen und Inhalte ein. Bei jedem Start aktualisiert es Datenbankschema, Backend-Gruppen und Sprachpaket und leert die Caches.
  • healthcheck.sh meldet „gesund“, sobald der Entrypoint fertig ist und PHP-FPM auf Port 9000 antwortet.
  • EXPOSE 9000 dokumentiert den FPM-Port. Veröffentlicht wird er nicht, nginx erreicht ihn über das interne Docker-Netz.
  • ENTRYPOINT und CMD wirken zusammen: Docker startet typo3-entrypoint php-fpm. Das Skript endet mit exec "$@", 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/html

Die 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.sh

Die Datei .dockerignore schließt Dateien vom Build-Kontext aus. Docker überträgt sie gar nicht erst an den Build.

  • Sicherheit: .env mit Passwörtern und Client-Secret landet nie in einem Image. Die Werte kommen zur Laufzeit über Compose.
  • Sauberkeit: Ein lokal vorhandenes vendor/, public/ oder var/ 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.