Appearance
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:
- Preflight: SSH-Alias erreichen und Stack-Pfad
~/stacks/appsprüfen. - rsync der Working-Copy →
/var/www/<service>/auf dem Server (persistente Dirs ausgeschlossen,--delete). docker compose build <service> && up -d <service>im Stack~/stacks/apps.- 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/upUmgebungen
| Umgebung | Branch | Trigger | Ziel |
|---|---|---|---|
| Local | Feature-Branches | ddev start | *.matev.ddev.site |
| CI-Tests | development, staging, PRs | Push/PR (Woodpecker) | nur Tests, kein Deploy |
| Production | main | Merge → Woodpecker deploy-production | 172.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 adminNutze 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.
| Service | Build | Post-Hooks |
|---|---|---|
admin (Filament) | rsync + docker compose build admin | migrate --force, db:seed DatabaseSeeder (idempotent, firstOrCreate), optimize:clear, filament:optimize |
b2b (Inertia/Vue) | rsync + build | migrate --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 | — |
storybook | Sonderfall: 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
mainschlägt fehl, wenn Quelle ≠staging. deploy-production(nurmain, push): Imagecomposer:2, installiertrsync+openssh-client, schreibt~/.ssh/config(Aliasmatev-deploy→172.16.111.40), installiert die gelockten Admin-Composer-Dependencies und startetadmin/vendor/bin/dep deploy production.
Voraussetzungen (einmalig, user-seitig)
- In Woodpecker (
ci.matev.eu, Loginforgejo-admin) das Repomatev-2026aktivieren („Add repository"). - Repo-Secret
deploy_ssh_key= privater~/.ssh/matev_deploy-Key (erreicht den Deploy-User auf172.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).
| Variable | admin | b2b | cms | staging |
|---|---|---|---|---|
APP_URL | https://admin.matev.eu | https://b2b.matev.eu | — | — |
OCTANE_SERVER | frankenphp | frankenphp | — | — |
DB_DATABASE | matev_admin | matev_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 unterhttps://admin.matev.eu/media/library/...ausgeliefert;public/mediaist 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.
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.Quelldateien wiederaufnehmbar übertragen (empfohlen):
bashrsync -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.gzQuell-DB einmalig auf dem Server anlegen und importieren:
bashssh 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-queueErst Probelauf, dann Vollimport direkt auf dem Server:
bashdocker 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=50Nach Abschluss der Queue verifizieren:
bashdocker 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-productiondurchgelaufen - [ ] Smoke-Tests:
- [ ]
https://admin.matev.eu/adminlädt + Login - [ ]
https://cms.matev.eu/api/queryantwortet auf KQL-POST ({"query":"site.title"}) - [ ]
https://staging.matev.eurendert Startseite mit echtem CMS-Inhalt - [ ]
https://b2b.matev.euLogin ohne 500 - [ ]
docker compose ps admin-queue→Up; Mediathek: neues PDF hochladen → nach wenigen Sekunden Vorschaubild
- [ ]
Verwandte Themen
- Git-Strategie — Branches + Protection
- Server-Setup / Stacks-Referenz
- Backup — DB-Dumps + Restic