Zum Hauptinhalt springen

Ausführung eines Shopware Containers

Einführung

Das docker-Recipe von Shopware richtet die Infrastruktur-Services ein (database, opensearch, mailer) und legt ein docker/Dockerfile in deinem Projekt ab, liefert aber keinen laufenden Anwendungs-Container. Dieser Guide behandelt den fehlenden Teil: ein Anwendungs-Image aus diesem Dockerfile bauen, in eine Registry pushen und in das mittwald Container Hosting deployen. Am Ende steht der Theme- und Plugin-Workflow, der etwas Sorgfalt braucht, weil der Runtime-Container effektiv schreibgeschützt ist.

Voraussetzungen

  • mStudio API-Token mit den erforderlichen Berechtigungen; siehe API-Token erhalten
  • Ein Shopware-Projekt auf deinem lokalen Rechner, entweder ein bestehendes oder ein neues (der nächste Abschnitt zeigt, wie du eins anlegst)
  • Composer und PHP lokal, um das Projekt zu erstellen und das Docker-Setup hinzuzufügen
  • Docker mit Buildx auf deinem lokalen Rechner, damit du für die Plattform linux/amd64 bauen kannst, auf der mittwald läuft
  • Die mittwald CLI (mw) installiert und eingeloggt; siehe die CLI-Dokumentation

TLS terminiert der mittwald-Ingress, der Container liefert also nur einfaches HTTP aus. Das Standard-Shopware-Image lauscht bereits auf Port 8000; du schaltest lediglich Caddys eingebautes automatisches HTTPS über die Umgebungsvariable CADDY_GLOBAL_OPTIONS ab (siehe Umgebung konfigurieren). Ein eigener Caddyfile oder Dockerfile-Änderungen sind nicht nötig.

Projekt einrichten

Wenn du bereits ein Shopware-Projekt hast, springe zum nächsten Schritt. Um von Grund auf zu starten, erstelle ein Projekt mit der Shopware-CLI:

user@local $ npx @shopware-ag/shopware-cli project create <folder>
user@local $ cd <folder>

Um eine bestimmte Shopware-Version zu pinnen, übergib sie als zweites Argument, zum Beispiel npx @shopware-ag/shopware-cli project create <folder> 6.6.7.0.

Füge als Nächstes das Docker-Setup hinzu. Das Paket shopware/docker installiert über ein Symfony-Flex-Recipe ein fertiges docker/Dockerfile in dein Projekt, sodass du das offizielle Build-Setup erhältst, statt eines von Hand zu schreiben:

user@local $ composer require shopware/docker shopware/deployment-helper

Das ist zugleich der empfohlene Weg, das Dockerfile aktuell zu halten: Da es aus einem Flex-Recipe stammt, zieht ein Update des Pakets die Upstream-Änderungen am Build nach.

Umgebung konfigurieren

Shopware benötigt zur Laufzeit einige Umgebungsvariablen. Gib sie dem Container mit, statt sie ins Image einzubacken, damit dasselbe Image über verschiedene Umgebungen hinweg funktioniert.

  • APP_URL: die öffentliche URL deines Shops, z. B. https://your-domain.example
  • APP_SECRET: das Symfony-Runtime-Secret; generiere eines mit openssl rand -hex 128
  • DATABASE_URL: der Connection-String für deinen Datenbank-Service
  • TRUSTED_PROXIES: auf REMOTE_ADDR setzen, damit Shopware dem mittwald-Ingress vertraut
  • CADDY_GLOBAL_OPTIONS: auf auto_https off setzen, damit der FrankenPHP-Webserver (Caddy) kein TLS verwaltet

CADDY_GLOBAL_OPTIONS=auto_https off ist der entscheidende Punkt, der das Standard-Image mittwald-tauglich macht. mittwald terminiert TLS am Ingress und leitet einfaches HTTP weiter, der Container darf also kein eigenes Zertifikats-Handling betreiben. Ohne diese Variable legt Caddy beim Start eine lokale Certificate Authority an — hier überflüssig und auf read-only- oder Nicht-Root-Runtimes ein Problem. Mit ihr liefert das Image einfaches HTTP auf Port 8000 aus und macht keinerlei Zertifikatsarbeit. Der Standard-Caddyfile stellt diese Variable bereits bereit, ein eigener Caddyfile ist also nicht nötig.

Das Anwendungs-Dockerfile

Das Dockerfile, das shopware/docker hinzugefügt hat, ist ein Multi-Stage-Build. Es nutzt ghcr.io/shopware/shopware-cli, um das Projekt zu bauen (shopware-cli project ci), und kopiert die gebaute Anwendung dann in das FrankenPHP-Runtime-Base ghcr.io/shopware/docker-base:${PHP_VERSION}-frankenphp.

Für mittwald musst du es nicht ändern. Das Base-Image lauscht bereits auf dem einfachen HTTP-Port 8000, und die Variable CADDY_GLOBAL_OPTIONS=auto_https off aus dem vorherigen Schritt hält Caddy aus dem TLS-Geschäft heraus. Baue und deploye das Dockerfile exakt so, wie shopware/docker es generiert hat — kein eigener Caddyfile, kein Patch der Runtime-Stage.

Image bauen

mittwald-Container laufen auf linux/amd64. Baue ein natives Image für den schnellen lokalen Test und ein dediziertes amd64-Image für das Deployment.

Für den lokalen Test:

user@local $ docker build \
-f docker/Dockerfile \
--build-arg PHP_VERSION=8.3 \
-t shopware-app:local \
--load \
.

Für das mittwald-kompatible Deployment-Image baust du für amd64 und pushst direkt in deine Registry (ersetze den Registry-Host durch deinen eigenen, siehe den nächsten Abschnitt):

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

Pinne die PHP-Version über das Build-Argument PHP_VERSION, damit das Base-Image (ghcr.io/shopware/docker-base:${PHP_VERSION}-frankenphp) zwischen den Builds nicht driftet. Wenn dein Projekt kommerzielle Store-Extensions zieht, übergib deine Composer-Zugangsdaten an den Build, wie im Shopware-Docker-Guide beschrieben.

Lokal mit Compose testen

Bevor du pushst, prüfe, ob das Image läuft. Richte den app-Service in deiner compose.yaml auf das lokale Image (shopware-app:local) und stelle sicher, dass sein environment CADDY_GLOBAL_OPTIONS: auto_https off enthält (neben APP_URL, DATABASE_URL und den übrigen); starte dann den Stack:

user@local $ docker compose -f compose.yaml -f compose.override.yaml up -d

Beobachte die Anwendungs-Logs, bis Shopware gebootet ist:

user@local $ docker compose logs -f app

Wenn der Stack läuft, initialisiere Shopware im Container:

user@local $ docker compose exec app vendor/bin/shopware-deployment-helper run

Lege bei Bedarf einen Admin-Benutzer an:

user@local $ docker compose exec app bin/console user:create admin

Registry anlegen

mittwald zieht dein Deployment-Image aus einer Registry, die deinem Projekt zugeordnet ist. Auch wenn du das amd64-Image im Build-Schritt oben schon gebaut und gepusht hast, muss die Registry zuerst existieren.

  1. Öffne dein Projekt im mStudio.
  2. Wähle den Menüpunkt "Container" in der Seitenleiste.
  3. Wechsle zum Tab "Registries".
  4. Klicke auf "Registry hinzufügen".
  5. Konfiguriere die Registry-URL und die Zugangsdaten.

Mehr zu privaten Registries findest du unter Private Registries verwenden.

Image pushen

Wenn du das Image bereits mit --push gebaut hast, überspringe diesen Schritt. Wenn du zuerst lokal gebaut hast und genau dieses Image übernehmen möchtest, tagge es für die Registry und pushe es:

user@local $ docker tag shopware-app:local docker.p-XXXXXX.project.space/shopware/shopware-app:8.3-frankenphp
user@local $ docker login docker.p-XXXXXX.project.space
user@local $ docker push docker.p-XXXXXX.project.space/shopware/shopware-app:8.3-frankenphp

Auf mittwald deployen

Erstelle den Container aus deinem gepushten Image, gib den HTTP-Port frei, mounte Volumes für Shopwares persistente Daten und route eine Domain darauf. Da das Runtime-Dateisystem schreibgeschützt ist und Container bei jedem Deploy neu erstellt werden, müssen die Verzeichnisse mit nutzergenerierten oder erzeugten Inhalten auf persistenten Volumes liegen, sonst gehen Medien, generierte Themes, Thumbnails und Sitemaps verloren. Die Beispiele unten mounten sie alle.

  1. Öffne dein Projekt im mStudio und wähle "Container".
  2. Klicke auf "Container erstellen".
  3. Wähle im Assistenten dein Image docker.p-XXXXXX.project.space/shopware/shopware-app:8.3-frankenphp.
  4. Gib die Umgebungsvariablen aus dem Konfigurationsschritt an und gib Port 8000 frei.
  5. Füge unter Volumes für jedes von Shopwares persistenten Verzeichnissen ein Mount hinzu und schließe dann den Assistenten ab.

TLS übernimmt der mittwald-Ingress, der Container selbst liefert also nur einfaches HTTP auf Port 8000 aus. Um ihn erreichbar zu machen, füge ein Ingress hinzu, das deine Domain auf den Container-Port mappt.

Persistente Volumes

Der Deploy-Schritt oben mountet für jedes Verzeichnis, das Shopware zu persistieren empfiehlt, ein Volume — also die, die nutzergenerierte oder erzeugte Inhalte aufnehmen. Ohne sie gehen diese Daten verloren, sobald der Container neu erstellt wird. Zur Referenz die Pfade:

  • /var/www/html/files
  • /var/www/html/public/theme
  • /var/www/html/public/media
  • /var/www/html/public/thumbnail
  • /var/www/html/public/sitemap

Theme- und Plugin-Workflow

Im mittwald-Runtime-Container ist /var/www/html effektiv nicht schreibbar. Alles, was Dateien erzeugt (ein Theme oder ein Plugin anlegen), muss zur Build-Zeit passieren, nicht zur Laufzeit.

Themes und Plugins hinzuzufügen ist in diesem Setup eine Build-Zeit-Aufgabe: Ihr Quellcode liegt in custom/plugins, ihre Assets in public/bundles — beides Teil des read-only Images, nicht der persistenten Volumes oben, die nur zur Laufzeit erzeugte Inhalte wie Medien und kompilierte Theme-Ausgaben enthalten. Extensions anzulegen oder zu installieren passiert also im Image-Build, nicht im laufenden Container. Die funktionierende Aufteilung ist:

  • Führe theme:create und plugin:create lokal aus.
  • Committe die erzeugten Dateien unter custom/plugins/....
  • Baue, pushe und deploye ein neues Image.
  • Führe im Container nur Befehle aus, die nicht in den Quellbaum schreiben: plugin:refresh, plugin:install --activate und theme:compile.

Checks nach dem Deployment

Sobald der Container läuft, prüfe Shopware darin:

user@local $ mw container exec <container-id> -- php /var/www/html/bin/console about
user@local $ mw container exec <container-id> -- php /var/www/html/bin/console system:check
user@local $ mw container exec <container-id> -- php /var/www/html/bin/console plugin:refresh
user@local $ mw container exec <container-id> -- php /var/www/html/bin/console cache:clear

Häufige Probleme

Only linux/amd64 supported

Das Image wurde für die falsche Architektur gebaut. Baue das Deployment-Image erneut mit --platform linux/amd64.

permission denied in /var/www/html

Etwas hat versucht, zur Laufzeit in den schreibgeschützten Quellbaum zu schreiben. Dafür gibt es zwei unterschiedliche Ursachen, prüfe, welche zutrifft:

  • Ein Build-Schritt wurde zur Laufzeit ausgeführt. Ein Theme oder Plugin anlegen, Extensions installieren oder jeder Befehl, der in den Quellbaum schreibt, muss während des Image-Builds passieren, nicht im deployten Container. Verschiebe diesen Schritt in den Build; siehe den Theme- und Plugin-Workflow oben.
  • Ein Verzeichnis, das legitim zur Laufzeit schreibt, ist nicht als Volume gemountet. Nutzergenerierte und erzeugte Inhalte (Medien, Thumbnails, generierte Themes, Sitemaps) werden zur Laufzeit geschrieben und müssen auf einem persistenten Volume liegen. Prüfe, ob der betroffene Pfad tatsächlich gemountet ist, wie unter Persistente Volumes oben aufgeführt.

Der Extension Store schlägt mit Unable to create a directory at bundles/… fehl

Das Installieren oder Aktivieren einer Extension aus dem Shopware Store (z.B. dem Plugin SwagExtensionStore selbst) schlägt mit einer Fehlermeldung wie dieser fehl:

Unable to create a directory at bundles/swagextensionstore.
mkdir(): Permission denied

Die Extension-Verwaltung zur Laufzeit schreibt nach public/bundles, das Teil des schreibgeschützten Images und kein gemountetes Volume ist – auf der mittwald-Runtime kann das daher nicht funktionieren. Extensions müssen stattdessen zur Build-Zeit über Composer in das Image gebacken werden, wie im Theme- und Plugin-Workflow oben beschrieben. Das ist zugleich Shopwares eigene Empfehlung: Der Extension-Management-Guide rät dazu, alle Extensions über Composer zu installieren, statt sie zur Laufzeit in der Administration zu verwalten.

Deaktiviere die Extension-Verwaltung zur Laufzeit, damit Shopware nicht mehr versucht, dorthin zu schreiben, und den nun funktionslosen Store vor Admin-Nutzern ausblendet. Füge deinem Projekt eine Konfigurationsdatei hinzu, damit sie zur Build-Zeit ins Image gebacken wird:

config/packages/mittwald.yaml
shopware:
deployment:
runtime_extension_management: false

Committe die Datei, baue das Image neu und deploye erneut. Verwalte Extensions stattdessen über Composer (composer require ...) und committe das Ergebnis. Die Einstellung runtime_extension_management setzt Shopware 6.6.4.0 oder neuer voraus; auf älteren Versionen hat sie keine Wirkung.

Could not find theme with name "Storefront"

Das Theme ist nicht in den Container gelangt, oder seine Sales-Channel-Zuordnung fehlt. Prüfe, ob das Theme im gebauten Image unter custom/plugins vorhanden ist, und kontrolliere die Parent-Abhängigkeit des Themes sowie die Sales-Channel-Theme-Zuordnung.

Nächste Schritte

  • Automatisiere die Build-, Push- und Deploy-Schritte in einer CI-Pipeline, die pro Release ein neues Image taggt, siehe den Guide über GitHub Actions.