TYPO3 in Docker zu betreiben und Deployments über GitLab CI zu automatisieren ist ein solides Setup: reproduzierbare Builds, saubere Trennung der Umgebungen und eine vollautomatische Auslieferungs-Pipeline. Dieser Artikel geht durch, wie die CI/CD-Pipeline für dieses Projekt aufgebaut ist — vom ersten Push bis zum laufenden Container auf dem Server.
Das Setup
Das Projekt fährt zwei Docker-Container: einen für den Webserver und einen für PHP. Beide werden in der GitLab-Pipeline gebaut und per SSH auf einen eigenen Server deployt. Die Pipeline hat drei Stages:
stages:
- test
- build
- deploy
Sowohl der build- als auch der deploy-Job sind auf den master-Branch beschränkt, damit nur reviewter, gemergter Code je auf den Server kommt. Ein eigener GitLab-Runner mit dem Tag typo3blogs führt alles aus.
Eine Pipeline, die läuft, ist mehr wert als eine perfekte Pipeline, die es noch nicht gibt. Fang einfach an; bau Komplexität erst dazu, wenn der Schmerz ohne sie real wird.
SSH-Authentifizierung
Jeder Job, der den Server anfasst, braucht SSH-Zugang. Statt das in jedem Job einzeln zu konfigurieren, richtet die Pipeline es einmal in before_script ein, damit es überall verfügbar ist:
before_script:
- which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
Der private Key liegt als GitLab-CI-Variable und landet nie im Repository. Das tr -d '\r' ist ausdrücklich erwähnenswert — SSH-Keys, die unter Windows bearbeitet oder kopiert wurden, bekommen still Carriage Returns, die ssh-add auf eine Weise kaputtmachen, die kryptische Fehler produziert. Sie hier zu entfernen verhindert das komplett.
Die gefährlichsten Bugs in einer Deployment-Pipeline sind die, die still scheitern. Ein Carriage Return, ein fehlender Zeilenumbruch, eine Variable, die richtig aussieht und es nicht ist — überprüf immer, dass deine Secrets wirklich funktionieren, bevor du sie Freitag um 23 Uhr brauchst.
Die Docker-Images bauen
Die Build-Stage baut beide Docker-Images und schreibt den aktuellen Commit-SHA als Image-Tag hinein. Damit ist jedes Image direkt auf den Code zurückführbar, aus dem es entstanden ist:
- docker build -t serie3/php_container:$CI_COMMIT_SHA --no-cache ./env/prod/PHP/
- docker build -t serie3/typo3blogs_web:$CI_COMMIT_SHA --no-cache ./env/prod/Web/
--no-cache sorgt dafür, dass jeder Build sauber startet — keine veralteten Layer, keine Überraschungen aus zwischengespeicherten Zwischenständen.
Images ohne Registry übertragen
Statt die Images in eine Container-Registry zu pushen und auf dem Server zu ziehen, exportiert die Pipeline sie als .tar-Archive und überträgt sie direkt per SCP. Damit bleibt das gesamte Deployment in einem kontrollierten privaten Netz, ohne externe Abhängigkeiten:
- docker save -o php_image.tar serie3/php_container:$CI_COMMIT_SHA
- scp php_image.tar username@server:~/pathtotpyo3/
- docker save -o web_image.tar serie3/typo3blogs_web:$CI_COMMIT_SHA
- scp web_image.tar username@server:~/pathtotpyo3/
Der Server lädt beide Images aus den übertragenen Archiven:
- ssh username@server 'docker load -i ~/pathtotpyo3/php_image.tar'
- ssh username@server 'docker load -i ~/pathtotpyo3/web_image.tar'
Keine Registry-Authentifizierung, keine Rate Limits, kein ausgehendes Internet auf dem Server nötig. Das Aufräumen alter .tar-Dateien und ungenutzter Docker-Images erledigt ein Cronjob auf dem Server, damit die Pipeline selbst schlank bleibt.
Die Deploy-Stage
Sobald die Images geladen sind, fährt die Deploy-Stage die aktualisierten Container hoch und führt die letzten TYPO3-Schritte aus:
- ssh username@server "cd ~/pathtotpyo3/ && docker-compose up -d --no-deps php_container"
- ssh username@server "cd ~/pathtotpyo3/ && docker-compose up -d --no-deps typo3blogs_web"
- ssh username@server "docker exec php_container sh -c 'cp -Rvf /var/www/code/* /var/www/site/web/'"
- ssh username@server "docker exec php_container composer install --no-dev --working-dir /var/www/site/web/"
Das --no-deps-Flag startet nur die angesprochenen Container neu, ohne die Datenbank anzufassen. Die aktualisierte docker-compose.yml — mit dem neuen Image-Tag, der beim Build per sed eingesetzt wurde — wird zusammen mit den Images übertragen, damit Compose-Datei und laufende Container immer synchron bleiben.
Das Gesamtbild
Zusammengesetzt löst jeder Push auf master einen vollautomatischen Zyklus aus: zwei Docker-Images bauen, mit dem Commit-SHA taggen, auf den Server schicken, laden, Container neu starten und die TYPO3-Codebase deployen. Keine manuellen Schritte, keine externen Dienste nötig, und jeder deployte Stand ist auf einen bestimmten Commit zurückführbar.
Komplexität ist kein Ersatz für Qualität. Die beste Pipeline ist die, die zuverlässig ausliefert.