Shopware Container Deployment: Warum das Image read-only bleibt
- Container-Image bei Shopware: Warum es sich nicht mehr ändert
- Shopware Docker-Recipe: Was composer require shopware/docker liefert – und was fehlt
- Shopware-Image bauen: TLS und Architektur vorher klären
- Shopware Volumes: Welche Laufzeitdaten persistent sein müssen
- Warum der Shopware Extension Store im Container nicht funktioniert
- Fazit: Ein Image ist wie ein Polaroid
Container-Image bei Shopware: Warum es sich nicht mehr ändert
Ich sammle Polaroid-Kameras. Was ich an ihnen liebe, ist ihre Endgültigkeit. du drückst den Auslöser, das Bild entwickelt sich, fertig. Nachschärfen geht nicht. Wenn du ein anderes Bild willst, musst du ein neues knipsen.
Ein Container-Image funktioniert genauso, das sagt schon der Name. Es ist ein Abbild deiner Anwendung, das beim Build entsteht und danach unverändert bleibt. Theoretisch war mir das klar, richtig verstanden habe ich es erst, als ich Shopware bei mittwald deployen wollte. Dabei ist mir eine Annahme aufgefallen, die ich jahrelang mit mir herumgetragen hatte: Ich dachte immer, dass ich Dateien schreiben darf.
Lass uns die kleinen Hürden abhaken, die das Image für mittwald braucht. Danach kommt die Frage, um die es eigentlich geht: Was muss aufs Bild, und was passiert danach drumherum?
Shopware Docker-Recipe: Was composer require shopware/docker liefert – und was fehlt
Shopware bringt ein eigenes Docker-Image mit. Mit dem Befehl composer require shopware/docker legst du ein docker/Dockerfile ins Projekt. Außerdem bekommst du die Infrastruktur-Services für Datenbank, OpenSearch und Mailer. Shopware empfiehlt diesen Weg auch, so übernimmt ein Paket-Update auch die Änderungen am Dockerfile aus dem Upstream.
Was das Recipe nicht liefert, ist ein laufender Anwendungscontainer. Das Dockerfile liegt dort und die Services drumherum starten, aber niemand baut daraus ein Image und bringt es auf die Plattform. Diese Lücke füllen wir jetzt zusammen.
Shopware-Image bauen: TLS und Architektur vorher klären
Das Shopware-Image nutzt FrankenPHP, und in FrankenPHP steckt Caddy. Caddy kümmert sich beim Start ungefragt um HTTPS, auf mittwald terminiert aber schon der Ingress das TLS. Zwei wollen also dieselbe Aufgabe übernehmen, und eine davon hat für ihre Zertifikate nicht einmal Schreibrechte. Eine Umgebungsvariable stellt das ab:
CADDY_GLOBAL_OPTIONS=auto_https offMit dieser Variable läuft das offizielle Standard-Image auf mittwald, ohne dass sich Caddy selbstständig ums TLS kümmern muss. So brauchst du dazu keine eigene Caddyfile und musst somit das Dockerfile auch nicht ändern. Die mitgelieferte Caddyfile liest die Variable automatisch aus, du musst sie also nur setzen. Das Image lauscht außerdem bereits auf Port 8000.
Es hat nicht lange gedauert, bis ich in die nächste Hürde gelaufen bin, denn mittwald-Container laufen auf linux/amd64. Wenn du wie ich auf einem Apple-Silicon-Rechner nativ baust, bekommst du ein arm64-Image. Beim Deployment erscheint dann die Meldung: Only linux/amd64 supported.
Ein sozusagen "zweigleisiges" Vorgehen ist hier sinnvoll. Für den lokalen Test baust du nativ, hier läuft dieser Build ohne Emulation. Für das Deployment baust du ein eigenes amd64-Image mit Buildx. Dieser Build läuft auf Apple Silicon und dauert daher deutlich länger.
user@local $ docker buildx build \
--platform linux/amd64 \
-f docker/Dockerfile \
--build-arg PHP_VERSION=8.3 \
-t docker.p-XXXXXX.project.space/shopware/shopware-app:8.3-frankenphp \
--push \
.Das Build-Argument PHP_VERSION pinnt das Base-Image, damit dir die PHP-Version zwischen zwei Builds nicht unter den Füßen wegdriftet. Und das ist es fast schon soweit!
Dann, bevor du pushst, mach einen lokalen Durchlauf. Stell im app-Service in Deiner compose.yaml das lokale Image ein. Meist gibt es Probleme mit dem Environment. Dort muss CADDY_GLOBAL_OPTIONS: auto_https off stehen. Sonst startet Caddy wieder mit seiner eigenen CA. Danach initialisierst du Shopware im Container.
user@local $ docker compose exec app vendor/bin/shopware-deployment-helper runDer Deployment-Helper prüft den Zustand der Datenbank. So erkennt er selbst, ob eine Installation oder eine Aktualisierung nötig ist. Startet der Shop in diesem Schritt, hast du die meisten Fehler schon gesehen. Solange du noch leicht darauf zugreifen kannst, bist du auf der sicheren Seite.
Shopware Volumes: Welche Laufzeitdaten persistent sein müssen
Beim Containerisieren stellt sich jedes Mal dieselbe Frage, ganz gleich ob es um Shopware, WordPress oder eine eigene Anwendung geht: Wo liegen die Bewegungsdaten, und was ist fix?
Fix ist alles, was aus dem Quellcode entsteht. Das steckt im Image. Bewegungsdaten entstehen zur Laufzeit und brauchen persistente Volumes, sonst sind sie beim nächsten Deployment weg. So weit die bekannte Zweiteilung. Bei Shopware sind es aber drei Kategorien, und die dritte ist die unangenehme.
Also zurück zu Shopware: Ein Teil der Laufzeitdaten ist im Shopware-Fall abgeleitet. Das sieht so aus: Die kompilierten Theme-Dateien liegen unter public/theme, die Thumbnails unter public/thumbnail und die Sitemap unter public/sitemap liegen im Volume, weil Shopware sie zur Laufzeit schreibt. Gehen sie verloren, kostet Dich das drei Befehle und etwas Geduld:
user@shop $ bin/console theme:compile
user@shop $ bin/console media:generate-thumbnails
user@shop $ bin/console sitemap:generateDer Rest hat kein Negativ, wenn wir in der Polaroid Analogie bleiben. Ein Produktbild unter public/media hat jemand hochgeladen, die Rechnungsdokumente unter files sind Belege. Beides kannst bzw. darfst Du nicht neu rendern, und darin sind diese Dateien ihren analogen Verwandten ähnlich: ein Polaroid ohne Negativ, weg ist weg.
Das ergibt das folgende Bild:
Für unseren Shopware-Fall bzw. die Mounts heißt das zwei Gruppen statt einer Liste. Regenerierbar sind die folgenden:
/var/www/html/public/theme
/var/www/html/public/thumbnail
/var/www/html/public/sitemapUnd unersetzlich diese hier:
/var/www/html/public/media
/var/www/html/filesEin Volume ist noch kein Backup. Und selbst wenn eines läuft, kommt beim Zurückholen die unangenehme Frage nach dem Zeitpunkt: Die Produktdaten liegen in der Datenbank, die Bilder liegen im Volume, und gesichert wird beides von verschiedenen Mechanismen. Holst du die zwei aus unterschiedlichen Momenten zurück, hast du Produkte ohne Bilder oder Bilder ohne Produkte. Das Album und das Verzeichnis müssen denselben Stand haben. Sieh also nach, was bei deinen Volumes gesichert wird und wie oft, bevor der erste echte Shop darauf läuft. Und wenn dir die Antwort nicht gefällt: public/media und files sind die beiden Pfade, bei denen man über externen Object Storage nachdenken könnte.
Somit ein kleiner Hint: Falls du diese Dateien gar nicht im Container haben möchtest, empfiehlt Shopware stattdessen externen S3-kompatiblen Object Storage. Wenn du das public-Filesystem entsprechend einstellst, werden Theme-, Asset- und Sitemap-Dateien dorthin verschoben. Dann brauchst du keine Volumes.
Warum der Shopware Extension Store im Container nicht funktioniert
Letzte Sonderlocke im Shopware-Fall: Der Extension Store will eine Extension installieren und schreibt dafür in public/bundles. Das ist Teil des read-only Images und kein Volume, soll auch keins sein.
Unable to create a directory at bundles/swagextensionstore.
mkdir(): Permission deniedAusgerechnet der Store, der Extensions verwalten soll, kann sich hier nicht selbst installieren.
Mein erster Gedanke war, das als Einschränkung der Plattform zu sehen. Das stimmt aber nicht. Shopwares eigener Guide zum Extension-Management rät dazu, Extensions über Composer zu installieren. Man soll das nicht während des Betriebs über die Administration machen. Der Container sorgt also nur dafür, dass man das tut, was in der Dokumentation ohnehin steht.
Damit Shopware es gar nicht erst versucht und den funktionslosen Store im Admin ausblendet, kommt eine Konfigurationsdatei ins Projekt und damit zur Build-Zeit ins Image:
shopware:
deployment:
runtime_extension_management: falseDiese Einstellung gilt erst ab Shopware 6.6.4.0. Ab dann kommen Extensions über composer require hinzu. Sie werden wie jede andere Abhängigkeit committed.
Fazit: Ein Image ist wie ein Polaroid
Jede Fehlermeldung auf dem Weg war irgendwo eine Variante desselben Satzes: Caddy wollte Zertifikate schreiben. Der Extension Store wollte Bundles schreiben. theme:create wollte Quellcode schreiben. Drei sehr unterschiedliche Symptome, doch im Edneffekt dieselbe Ursache. Was aufs Bild muss, entscheidest Du beim Build. Was danach entsteht, gehört ins Album, und ein Teil davon hat kein Negativ.
Im Developer Portal sind der gesamte Weg mit allen Schritten sowie jede Fehlermeldung mit ihrer Ursache aufgeführt. Wenn du das Ganze danach nicht mehr von Hand machen willst, zeigt dir der Guide zum Container-Deployment mit GitHub Actions, wie Build und Push in eine Pipeline integriert werden. Da geht es beim nächsten Mal weiter.
Kommentar hinzufügen