Skip to content

Deployment ​

Übersicht ​

MATEV 2026 wird per Deployer (deploy.php, Tool: deployer.org) ausgerollt. Deployer bildet den erprobten sync-to-server.sh-Mechanismus als Tasks ab:

  1. Preflight: SSH-Alias erreichen und Stack-Pfad ~/stacks/apps prüfen.
  2. rsync der Working-Copy → /var/www/<service>/ auf dem Server (persistente Dirs ausgeschlossen, --delete).
  3. docker compose build <service> && up -d <service> im Stack ~/stacks/apps.
  4. Post-Hooks (nur admin/b2b): php artisan migrate --force, ggf. db:seed, optimize:clear, filament:optimize.

Es wird kein Image in eine Registry gepusht — gebaut wird direkt auf dem Server aus dem rsync'ten Code.

Push auf main ──► Forgejo (git.matev.eu) ──webhook──► Woodpecker (ci.matev.eu)
                                                          │
                                          Tests grün → Step "deploy-production"
                                                          │  composer:2 + rsync + ssh
                                                          ▼
                                           admin/vendor/bin/dep deploy production
                                                          │  (SSH-Alias matev-deploy)
                                                          ▼
                                              Server  172.16.111.40
                                              rsync → /var/www/<svc>  →  docker compose build/up

Umgebungen ​

UmgebungBranchTriggerZiel
LocalFeature-Branchesddev start*.matev.ddev.site
CI-Testsdevelopment, staging, PRsPush/PR (Woodpecker)nur Tests, kein Deploy
ProductionmainMerge → Woodpecker deploy-production172.16.111.40 (`admin

Ein Server

Es gibt keinen separaten Staging-Server. staging.matev.eu zeigt nur auf den öffentlichen Proxy. Der Branch staging ist ein reines Test-/Gate vor main. Alles läuft auf der Production-Box 172.16.111.40.

Deploy-Ziel ​

deploy.php definiert den Host production über den SSH-Alias matev-deploy → HostName 172.16.111.40, User deploy, Key ~/.ssh/matev_deploy. Sowohl lokal (~/.ssh/config) als auch in der CI (generierter ~/.ssh/config-Block) löst der Alias auf dieselbe IP 172.16.111.40 auf — nie über matev.eu.

Deployer-Tasks (deploy.php) ​

bash
# Aus dem Repo-Root, Deployer kommt als gelockte dev-dep von admin:
admin/vendor/bin/dep deploy production          # alle Services
admin/vendor/bin/dep deploy:cms production       # nur cms
admin/vendor/bin/dep deploy:admin production     # nur admin

Nutze bevorzugt admin/vendor/bin/dep statt eines globalen dep. So läuft lokal und in CI exakt die Version aus admin/composer.lock.

Abgedeckte Services: admin, cms, b2b, docs, staging, storybook.

Vor jedem einzelnen Deploy-Task läuft deploy:preflight. Der Check bricht mit kurzem SSH-Timeout ab, wenn der Alias matev-deploy nicht erreichbar ist. Ein ssh: connect to host 172.16.111.40 port 22: Operation timed out bedeutet daher nicht "Deployer kaputt", sondern: VPN/privates Netz, Route, Firewall oder SSH-Alias prüfen.

ServiceBuildPost-Hooks
admin (Filament)rsync + docker compose build adminmigrate --force, db:seed DatabaseSeeder (idempotent, firstOrCreate), optimize:clear, filament:optimize
b2b (Inertia/Vue)rsync + buildmigrate --force, optimize:clear
cms (Kirby)rsync (content/media/accounts ausgeschlossen) + build—
docs (VitePress)rsync (.vitepress/dist+cache+backups aus) + build—
staging (Nuxt)rsync (.output/.nuxt aus) + build—
storybookSonderfall: rsync storybook/ + shared/ → /var/www, Build-Context /var/www—

Persistente Dirs bleiben serverseitig (rsync --exclude): .env, auth.json, Laravel-storage/ (inkl. der Mediathek-Dateien unter admin/storage/app/public), Kirby-content/media/site/accounts.

Woodpecker-Pipeline (.woodpecker.yml) ​

Eine zentrale Pipeline im Monorepo, pfadbasierte Trigger:

  • Tests (alle Branches & PRs): admin → Pint, PHPStan, Pest (mit MariaDB-Service), Bun-Build; b2b → Pint/PHPStan/Pest (sofern vorhanden) + Bun-Build; cms → composer validate; staging/storybook/docs → Bun-Builds.
  • Guard: PR nach main schlägt fehl, wenn Quelle ≠ staging.
  • deploy-production (nur main, push): Image composer:2, installiert rsync+openssh-client, schreibt ~/.ssh/config (Alias matev-deploy → 172.16.111.40), installiert die gelockten Admin-Composer-Dependencies und startet admin/vendor/bin/dep deploy production.

Voraussetzungen (einmalig, user-seitig) ​

  1. In Woodpecker (ci.matev.eu, Login forgejo-admin) das Repo matev-2026 aktivieren („Add repository").
  2. Repo-Secret deploy_ssh_key = privater ~/.ssh/matev_deploy-Key (erreicht den Deploy-User auf 172.16.111.40).

Service-Env (im apps/docker-compose.yml gesetzt) ​

Nicht über eine zentrale apps/.env, sondern pro Container im Compose. Persistente Secrets (APP_KEY, DB-PW) liegen in /var/www/<svc>/.env auf dem Server (rsync-excluded).

Variableadminb2bcmsstaging
APP_URLhttps://admin.matev.euhttps://b2b.matev.eu——
OCTANE_SERVERfrankenphpfrankenphp——
DB_DATABASEmatev_adminmatev_b2b——
KQL (Kirby)———KIRBY_URL/KIRBY_API_* (Build-ARG für nuxt-kql)

Octane-Hinweis: Der admin-Container startet FrankenPHP/Octane mit --admin-port=2019 — ohne diese Flag errechnet Octane einen negativen Admin-Port (2019 + (port − 8000)) und crasht (→ 502). Vgl. admin/Dockerfile.

Mediathek: Queue-Worker, Vorschaubilder, Pimcore-Import ​

Queue-Worker. admin-queue läuft im Stack neben admin — gleiches Image (matev/admin:latest), gleiche Umgebung, gleicher Storage, nur ein anderer Prozess (queue:work redis --queue=default,imports). deploy:admin baut das Image, startet beide Dienste neu und schickt queue:restart. Ohne Worker entsteht nach Upload oder Import nur das thumb-Vorschaubild; Kachel, Vorschau, Hero sowie PDF-/SVG-Vorschauen bleiben in der Queue liegen.

Beim ersten Deploy mit diesem Stand muss die geänderte ~/stacks/apps/docker-compose.yml (Dienst admin-queue, IMAGE_DRIVER) auf den Server — der Deploy synct den Stack nicht selbst:

bash
scp home/bengel/stacks/apps/docker-compose.yml matev-deploy:~/stacks/apps/docker-compose.yml
admin/vendor/bin/dep deploy:admin production
ssh matev-deploy 'cd ~/stacks/apps && docker compose ps admin admin-queue && docker compose logs --tail=20 admin-queue'

Bildwerkzeuge. Das Admin-Image bringt imagick, Ghostscript, die ImageMagick-Delegates für PDF und SVG sowie ffmpeg für das Standbild aus Videos mit (admin/Dockerfile). Prüfen:

bash
ssh matev-deploy 'docker exec admin php -r "echo implode(\",\", array_intersect([\"PDF\",\"SVG\",\"HEIC\",\"TIFF\"], Imagick::queryFormats()));" && docker exec admin ffmpeg -version | head -1'

Nach dem ersten Deploy mit ffmpeg bekommen vorhandene Videos ihr Standbild mit media-library:regenerate --only-missing (siehe oben).

Migration + Seeder. 2026_09_15_144040_add_system_flag_to_image_formats_table läuft über migrate; der MediathekSeeder (über DatabaseSeeder) legt die Admin-Bildformate thumb, mini, tile, preview, hero an. Vorhandene Maße bleiben unangetastet. Bestehende Medien bekommen die neuen Formate erst mit:

bash
ssh matev-deploy 'docker exec admin-queue php artisan media-library:regenerate --only-missing'

Asset-Import auf dem Server (Reihenfolge egal, beide Läufe gleichen die Verknüpfungen ab):

Die Ablage ist bewusst zweigeteilt:

  • /home/deploy/import/assets/ ist das private, nicht öffentlich erreichbare Eingangsverzeichnis fuer einen Import. Nach einem erfolgreichen Vollimport kann dieser Bestand archiviert oder entfernt werden.
  • /var/www/admin/storage/app/public/library/ ist die dauerhafte Mediathek fuer importierte und direkt hochgeladene Dateien. Sie wird im Browser unter https://admin.matev.eu/media/library/... ausgeliefert; public/media ist lediglich der Laravel-Symlink auf den persistenten Storage.
  • Neue Uploads werden innerhalb von library/ in dem Ordner abgelegt, den der Redakteur in der Mediathek ausgewählt oder dort angelegt hat. Die Herkunft einer Datei beeinflusst ihre spätere URL nicht.
  1. Vollständigen Quelldatenbank-Dump und das originale Asset-Verzeichnis bereitstellen. Ein bereits importiertes Laravel-storage/ ersetzt die Quelldateien nicht, da darin Pfade und fehlende Lazy-Assets nicht zuverlässig rekonstruiert werden können.

  2. Quelldateien wiederaufnehmbar übertragen (empfohlen):

    bash
    rsync -azh --partial --info=progress2 \
      /lokaler/pfad/zu/assets/ \
      matev-deploy:/home/deploy/import/assets/
    rsync -azh --partial --info=progress2 \
      docs/backups/2026-05-21/pimcore-source-db.sql.gz \
      matev-deploy:/var/www/admin/import/

    Mit SFTP dieselben Ziele verwenden:

    text
    /home/deploy/import/assets/
    /var/www/admin/import/pimcore-source-db.sql.gz
  3. Quell-DB einmalig auf dem Server anlegen und importieren:

    bash
    ssh matev-deploy
    cd ~/stacks/apps
    docker compose exec -T apps-db sh -lc 'mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" -e \
      "CREATE DATABASE IF NOT EXISTS matev_pimcore_source CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; GRANT ALL ON matev_pimcore_source.* TO '\''matev'\''@'\''%'\'';"'
    gzip -dc /var/www/admin/import/pimcore-source-db.sql.gz | \
      docker compose exec -T apps-db sh -lc 'mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" matev_pimcore_source'
    docker compose up -d --force-recreate admin admin-queue
  4. Erst Probelauf, dann Vollimport direkt auf dem Server:

    bash
    docker compose exec -T admin php artisan import:pimcore --dry-run
    docker compose exec -T admin php artisan import:assets --mode=eager --sample=10 --dry-run
    docker compose exec -T admin php artisan import:pimcore
    docker compose exec -T admin php artisan import:assets --mode=eager --queued --chunk=50
  5. Nach Abschluss der Queue verifizieren:

    bash
    docker compose logs -f admin-queue
    docker compose exec -T admin php artisan import:assets:verify

    „fehlend" und „überzählig" müssen 0 sein, „Zielobjekt fehlt" soll gegen 0 gehen. Der Prüflauf warnt zusätzlich, wenn eine Ordner-Regel ihren Taxonomie-Begriff (z. B. dam-typ) nicht findet.

Probeläufe mit --sample=10 an beiden Importen. Nie die lokale DB einspielen — die Taxonomie auf Production ist Handarbeit.

Manueller Deploy / Rollback ​

bash
# Vorabcheck ohne rsync/build:
admin/vendor/bin/dep deploy:preflight production

# Manuell (z.B. Kirby-Update), wenn die Pipeline nicht genutzt wird:
admin/vendor/bin/dep deploy:cms production

# Rollback: kein Registry-Tag-Rollback. Stattdessen im Git revert + redeploy,
# oder DB aus Backup (~/db-backups/ auf dem Server, mysqldump-Stände).

Deploy-Checkliste ​

  • [ ] CI grün (Pint/PHPStan/Pest/Bun-Build)
  • [ ] PR staging → main (Guard ok, Checks grün) gemerged
  • [ ] deploy-production durchgelaufen
  • [ ] Smoke-Tests:
    • [ ] https://admin.matev.eu/admin lädt + Login
    • [ ] https://cms.matev.eu/api/query antwortet auf KQL-POST ({"query":"site.title"})
    • [ ] https://staging.matev.eu rendert Startseite mit echtem CMS-Inhalt
    • [ ] https://b2b.matev.eu Login ohne 500
    • [ ] docker compose ps admin-queue → Up; Mediathek: neues PDF hochladen → nach wenigen Sekunden Vorschaubild

Verwandte Themen ​