Skip to content

Stacks-Referenz ​

Pro Stack: was läuft, welche Volumes, welche Env-Vars, was sichern. Alle Stacks hängen am externen Docker-Netz gateway.

home/bengel/stacks/                ← Compose + .env + persistente Volumes
├── proxy/    → caddy
├── mgmt/     → portainer (off-domain), matomo (+db)
├── git/      → forgejo (+db), woodpecker (+agent)
└── apps/     → admin, cms, b2b, staging, storybook, docs, search-stack,
              + Sub-Compose: penpot-compose.yml; eigener Stack taiga/

/var/www/                          ← Code-Webroots pro Service (bengel:www-data 2775)
├── admin/   cms/   b2b/   staging/   docs/   storybook/
├── design/  project/  tracking/      (Volumes für Tools)
├── shared/  frontend/  portal/        (Schemas / Legacy)

proxy/ ​

Zweck: zentraler Reverse-Proxy mit Auto-SSL für alle *.matev.eu-Hosts.

ContainerImageFunktion
caddycaddy:2.11.2Reverse-Proxy, Auto-SSL, HTTP/3

Caddyfile: zentrale Routing-Tabelle (home/bengel/stacks/proxy/Caddyfile) — eine Zeile pro Service. Nach jeder Änderung:

bash
docker compose -f ~/stacks/proxy/docker-compose.yml exec caddy \
    caddy reload --config /etc/caddy/Caddyfile

Volumes (zu sichern):

PfadInhalt
caddy_data (Docker-Volume)ausgestellte Let's-Encrypt-Zertifikate, OCSP-Stapling-Cache
caddy_config (Docker-Volume)Caddy-Runtime-Config

TIP

caddy_data ist nicht streng kritisch — bei Verlust holt Caddy alle Zertifikate beim ersten Request neu. Aber Let's Encrypt hat Rate-Limits (50 Zertifikate / Domain / Woche), daher trotzdem sichern.

Ports (Host): 80, 443, 443/udp.


mgmt/ ​

Zweck: Container-Cockpit + Web-Analytics.

ContainerImageFunktion
portainerportainer/portainer-ce:latestUI für alle Stacks (Logs, Restart, Shell)
matomomatomo:latestWeb-Analytics
matomo-dbmariadb:10.11Matomo-Datenbank

Volumes (zu sichern):

PfadInhaltRestore-Kritisch
./portainer_data/Portainer-DB (Users, Endpoints, Settings)mittel — kann neu eingerichtet werden
./matomo_data/Matomo-Webroot (Plugins, config/config.ini.php)hoch — Lizenz, Plugins, Custom-Configs
./matomo_db_data/Matomo-MariaDB-Datendateienkritisch — alle Tracking-Daten

Env-Vars (mgmt/.env):

ini
MATOMO_DB_PASSWORD=<sicheres-passwort>
MATOMO_DB_ROOT_PASSWORD=<sicheres-root-passwort>

Ports (Host):

  • 9000 — Portainer HTTP (intern, nur via UFW-Whitelist oder VPN)
  • 9443 — Portainer HTTPS (Self-Signed-TLS, nur via UFW-Whitelist oder VPN)
  • keine weiteren — Matomo läuft hinter Caddy

Routen: https://analytics.matev.eu → matomo:80. Portainer ist nicht in der Caddyfile — direkt https://<server-ip>:9443.

DB-Backup: siehe mgmt/db-backup.sh — dumpt Matomo-DB nach mgmt/dumps/, der Ordner wird vom Volume-Backup mitgenommen.


git/ ​

Zweck: Git-Hosting + CI. Liegt unter ~/stacks/git (User bengel) und ist als Repo matev/infra versioniert (Secrets via .env, gitignored; .env.example als Vorlage).

ContainerImageFunktion
forgejocodeberg.org/forgejo/forgejo:15-rootlessGit-Hosting (Web-UI + interner SSH-Server)
forgejo-dbpostgres:15-alpineForgejo-Datenbank
woodpeckerwoodpeckerci/woodpecker-server:v3CI-Server (Web-UI)
woodpecker_agentwoodpeckerci/woodpecker-agent:v3CI-Worker

Kein separater forgejo-runner — CI läuft über Woodpecker (der Agent ist der Runner). Woodpecker-Images sind auf v3 gepinnt: das :latest-Tag wurde von Woodpecker entfernt (Container beendet sich sonst mit einer Tag-Hinweis-Meldung → Crash-Loop).

Volumes (zu sichern):

PfadInhaltRestore-Kritisch
./postgres_forgejo/Forgejo-Postgres-Datenkritisch — Repos-Metadaten, Issues, PRs, OAuth-Apps, User
./forgejo_data/Repos, LFS, Avatars, Attachmentskritisch
./forgejo_config/app.ini etc.hoch
./woodpecker_data/Woodpecker-SQLite + Build-Logsmittel — muss UID 1000 gehören (sonst kann der v3-Container die SQLite-DB nicht anlegen)

Env-Vars (git/.env, alle Secrets dort — nicht ins Git):

ini
FORGEJO_DB_PASSWORD=<sicher>
# Forgejo-Security (Pflicht! sonst hängt Forgejo im Installer-Modus / API 404):
FORGEJO_SECRET_KEY=<forgejo generate secret SECRET_KEY>
FORGEJO_INTERNAL_TOKEN=<forgejo generate secret INTERNAL_TOKEN>
# Mailer (Forgejo → bengel.digital):
FORGEJO_MAILER_PASSWD=<mailbox-pw>
# Woodpecker ↔ Forgejo OAuth2-App:
WOODPECKER_FORGEJO_CLIENT=<oauth2-client-id>
WOODPECKER_FORGEJO_SECRET=<oauth2-client-secret>
WOODPECKER_AGENT_SECRET=<random-32-byte>
WOODPECKER_ADMIN=forgejo-admin

Im docker-compose.yml zusätzlich gesetzt: FORGEJO__security__INSTALL_LOCK=true (sonst zeigt Forgejo die Installer-Seite und die /api/v1-Routen sind nicht gemountet), FORGEJO__server__ROOT_URL=https://git.matev.eu/, sowie der Mailer (PROTOCOL=smtps, SMTP_ADDR=bengel.digital, SMTP_PORT=465, USER/FROM=matev-git@bengel.digital).

Routen: https://git.matev.eu → forgejo:3000, https://ci.matev.eu → woodpecker (Port 8000). Forgejo ist nur über HTTPS erreichbar; es ist kein Host-SSH-Port veröffentlicht (docker port forgejo ist leer) → Git push/pull läuft über HTTPS mit Token, nicht über SSH.

Login/Admin: Forgejo-Admin = forgejo-admin (E-Mail bengel@bengel.digital). Woodpecker hat keinen eigenen Login — nur Forgejo-OAuth.

Woodpecker-Agent braucht Docker-Socket (/var/run/docker.sock) — kann damit beliebige Container starten und den Host kontrollieren. Niemals untrusted Repos Zugriff geben.


apps/ ​

Zweck: alle Anwendungen + Search-Stack.

ContainerImage / Build-ContextFunktionDomain
adminbuild: /var/www/admin (FrankenPHP)Filament-PIMadmin.matev.eu
cmsbuild: /var/www/cms (FrankenPHP)Kirby CMS + GraphQLcms.matev.eu
b2bbuild: /var/www/b2b (FrankenPHP)Livewire-Händlerportalb2b.matev.eu
stagingbuild: /var/www/staging (Bun)Nuxt 4 Marketing-Frontendstaging.matev.eu
docsbuild: /var/www/docs (Bun)Vitepress-Dokudocs.matev.eu
storybookbuild: /var/www/storybook (Bun → nginx)statisches Storybook-Buildstorybook.matev.eu
apps-dbmariadb:11DB für admin + b2b(intern)
apps-redisredis:7-alpineCache, Queue, Session(intern)
meilisearchgetmeili/meilisearch:v1.16Schicht-B-Suche (Frontend)(intern)
opensearchopensearchproject/opensearch:2.18.0Schicht-C (Geo, RAG, Konfigurator, Embeddings)(intern)
ollamaollama/ollama:latestLLM-Provider (lokal)(intern)
penpot-*(Sub-Compose penpot-compose.yml)Design-Tooldesign.matev.eu
taiga-*(eigener Stack home/bengel/stacks/taiga/)Projektmanagementproject.matev.eu

Wichtige Konvention

  • Source-Code liegt in /var/www/<service>/ — Eigentum bengel:www-data 2775
  • Stack-Definition (Compose, .env, Daten) liegt in home/bengel/stacks/apps/
  • Build-Context im docker-compose.yml zeigt auf /var/www/<service>/
  • Bind-Mounts decken nur veränderliche Pfade (storage/, content/, media/, .env) ab
  • Container-User auf Host-GID 33 angepasst (siehe dockerfiles-reference/admin.Dockerfile)

Volumes (zu sichern):

PfadInhaltRestore-Kritisch
~/stacks/apps/apps_db_data/MariaDB-Datendateien (matev_admin + matev_b2b)kritisch
~/stacks/apps/meili_data/Meilisearch-Indizes (Frontend-Suche)hoch — sonst lange Reindex-Dauer
~/stacks/apps/opensearch_data/OpenSearch-Indizes (Geo, RAG, Embeddings)hoch
~/stacks/apps/ollama_data/Ollama-Modelle (mehrere GB)mittel — lassen sich neu pullen
~/stacks/apps/penpot_postgres/Penpot-Datenhoch
~/stacks/apps/penpot_assets/Penpot-Assetshoch
/var/www/admin/storage/Filament-Uploads (storage/app/public)kritisch
/var/www/cms/content/Kirby-Content (Markdown + YAML)kritisch — der CMS-Inhalt
/var/www/cms/media/Kirby-Mediakritisch
/var/www/cms/site/accounts/Kirby-Userhoch
/var/www/b2b/storage/Livewire-Uploadskritisch
~/stacks/apps/apps_redis_data/Cache, Queueniedrig — verzichtbar

Env-Vars (apps/.env):

ini
APPS_DB_ROOT_PASSWORD=<sicher>
APPS_DB_PASSWORD=<sicher>
ADMIN_APP_KEY=base64:<via php artisan key:generate --show>
B2B_APP_KEY=base64:<via php artisan key:generate --show>
KIRBY_LICENSE=<lizenz-key oder leer>
MEILISEARCH_MASTER_KEY=<sicher>
OPENSEARCH_ADMIN_PASSWORD=<sicher, mind. 8 chars + sonderzeichen>
PENPOT_DB_PASSWORD=<sicher>

Filament-Stack-Inhalt (admin) ​

Was im Image enthalten ist (Stand des /var/www/admin/-Codes):

  • Filament 5.6 + Laravel 13.6 + PHP 8.4 (FrankenPHP/Octane)
  • Spatie: laravel-tags, laravel-settings, laravel-permission
  • bezhansalleh/filament-shield (Permission-UI im Panel)
  • laravel/sanctum (REST-API + Cross-Domain-Auth gegen staging.matev.eu und b2b.matev.eu)
  • 22 Pimcore-Importer (php artisan import:pimcore --phase=1..5)
  • Search-Stack-Anbindung: laravel/scout (→ meilisearch), OpenSearch-Service, Ollama-Provider
  • Console-Commands: import:pimcore, blueprints:generate, opensearch:setup, ollama:setup, search:reindex, search:index-all
  • Spatie-Media-Library Pro-Vorbereitung (Migration + Trait, Lizenz nachträglich)

Kirby + GraphQL ​

GraphQL kommt über das Plugin getkirby/kql, Endpoint dann unter https://cms.matev.eu/api/query. Nuxt-Staging holt sich die Daten via useFetch('/api/query', { method: 'POST', body: { query } }) — definiert in staging/composables/useCms.ts.

Taiga + Penpot ​

Penpot fährt als Sub-Compose im apps/-Stack, Taiga steht in einem eigenen Stack-Verzeichnis (zu viele Container für ein Sub-Compose; sauberere Pfad-Trennung der Volumes):

bash
# Penpot — Sub-Compose im apps/-Stack
cd ~/stacks/apps
docker compose -f penpot-compose.yml up -d

# Taiga — eigener Stack (taigaio/taiga-docker)
# https://github.com/taigaio/taiga-docker
# taiga-gateway (nginx) hängt zusätzlich am externen `gateway`-Netz.
cd ~/stacks/taiga
cp .env.example .env       # Secrets setzen
docker compose up -d
# Erstes-Mal: Superuser anlegen
docker compose exec taiga-back python manage.py createsuperuser

Stack-Operations-Cheatsheet ​

bash
# Status aller Stacks
for s in proxy mgmt git apps; do
  echo "=== $s ==="
  docker compose -f ~/stacks/$s/docker-compose.yml ps
done

# Einzelnen Container neu bauen + starten (Code in /var/www/admin/ geändert)
cd ~/stacks/apps && docker compose build admin && docker compose up -d admin
docker compose exec admin php artisan octane:reload

# Logs streamen
docker compose -f ~/stacks/apps/docker-compose.yml logs -f admin

# Shell im Container
docker compose -f ~/stacks/apps/docker-compose.yml exec admin sh

# Caddy-Reload (kein Container-Restart nötig)
docker compose -f ~/stacks/proxy/docker-compose.yml exec caddy \
    caddy reload --config /etc/caddy/Caddyfile

# Komplett-Rebuild eines Stacks (Volumes bleiben erhalten, sofern benannt)
cd ~/stacks/apps && docker compose down && docker compose up -d --build

Update-Workflow ​

bash
# 1) Stack-Definitionen aus Git ziehen
cd ~/stacks && git pull

# 2) Source-Code pro Service aktualisieren
cd /var/www/admin && git pull         # bzw. rsync vom Dev-Rechner
cd /var/www/cms   && git pull
# ...

# 3) Externe Images aktualisieren (matomo, mariadb, postgres, …)
cd ~/stacks/mgmt && docker compose pull && docker compose up -d
cd ~/stacks/git  && docker compose pull && docker compose up -d
cd ~/stacks/apps && docker compose pull && docker compose up -d

# 4) Selbstgebaute Images neu bauen
cd ~/stacks/apps && docker compose build admin cms b2b staging docs
docker compose up -d
docker compose exec admin php artisan octane:reload
docker compose exec b2b   php artisan octane:reload

In Portainer wird der Restart sichtbar; die UI nicht zum Editieren der Stacks benutzen — Source of Truth bleibt das Git-Repo.