From 36e7d025395f0160765b2028b57a20e7a742aa48 Mon Sep 17 00:00:00 2001 From: Simon Kuehn Date: Tue, 19 May 2026 11:22:53 +0000 Subject: [PATCH] docs: add Docker layout and service interaction documentation Covers all 8 services, image/Dockerfile, PHP config, FPM pool tuning, volume layout, network topology, startup order, local dev override, operational commands, and Gitea Actions CI. Co-Authored-By: Claude Sonnet 4.6 --- docs/docker.md | 264 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 docs/docker.md diff --git a/docs/docker.md b/docs/docker.md new file mode 100644 index 0000000..4faefaf --- /dev/null +++ b/docs/docker.md @@ -0,0 +1,264 @@ +# Docker Layout & Service-Zusammenspiel + +--- + +## Services im Überblick + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Internet │ +└────────────────┬────────────────────────────────────────────────┘ + │ :80 / :443 + ┌──────▼──────┐ + │ caddy │ Reverse Proxy, Auto-HTTPS, gzip, HSTS + └──────┬──────┘ + │ php_fastcgi app:9000 + ┌──────▼──────┐ ┌────────────────┐ + │ app │◄────────►│ postgres :5432│ + │ PHP-FPM │ └────────────────┘ + └──────┬──────┘ ┌────────────────┐ + │ │ redis :6379 │ + ┌──────▼──────────┐ └────────────────┘ + │ Redis Streams │◄─────────────────────┘ + │ (3 transports) │ + └──┬───────┬──────┘ + ┌────────▼──┐ ┌──▼──────────┐ ┌──────────────┐ ┌──────┐ + │ worker-ai │ │worker-orders│ │worker-channel│ │ cron │ + └───────────┘ └─────────────┘ └──────────────┘ └──────┘ +``` + +--- + +## Service-Beschreibungen + +### `app` — PHP-FPM (Web) + +- Image: eigenes Build aus `docker/app/Dockerfile` (PHP 8.4-FPM-Alpine) +- Empfängt FastCGI-Requests von Caddy auf Port 9000 +- Schreibt in `var/` (Cache, Logs, Uploads), mounted als Bind-Mount +- User `1000:1000`, `HOME=/tmp` — kein Root, keine root-owned Dateien auf dem Host + +**Abhängigkeiten beim Start:** +``` +app → postgres (healthcheck: pg_isready) +app → redis (healthcheck: redis-cli ping) +``` + +### `caddy` — Reverse Proxy + +- Image: `caddy:2-alpine` +- Terminiert TLS (Let's Encrypt, automatisch erneuert) auf Port 80/443 +- Leitet alle PHP-Requests per `php_fastcgi app:9000` weiter +- Serviert statische Dateien direkt aus `/var/www/public` (`file_server`) +- Mounted das Projekt-Verzeichnis read-only für statische Assets +- Security-Header: `Strict-Transport-Security`, `X-Content-Type-Options`, `X-Frame-Options` +- Caddyfile: `docker/caddy/Caddyfile` + +**Domain-Konfiguration in `Caddyfile`:** +``` +ss3k.schaunwama.de → PHP-FPM app:9000 (SuperSeller3000) +erpstaging.* → reverse_proxy 172.18.0.1:8080 (externes Frappe — nur lokal) +``` + +> Hinweis: `erpstaging.*`-Block ist lokal/staging — in Prod läuft Frappe extern, diesen Block dann entfernen oder auskommentieren. + +### `postgres` — Datenbank + +- Image: `postgres:17-alpine` +- Volume: `postgres_data` (persistent, überlebt `docker compose down`) +- Healthcheck verhindert, dass `app` startet bevor Postgres bereit ist +- Kein Port nach außen in Prod (nur in `docker-compose.override.yml` für lokales Psql) + +### `redis` — Queue + Cache + +- Image: `redis:7-alpine` +- Passwort via `REDIS_PASSWORD` env var (aus `.env`) +- Volume: `redis_data` (persistent — Messenger-Queues überleben Neustart) +- Healthcheck: `redis-cli -a $REDIS_PASSWORD ping` +- Kein Port nach außen in Prod + +### `worker-ai` — AI-Pipeline-Worker + +```bash +php bin/console messenger:consume ai_pipeline --time-limit=3600 --memory-limit=256M +``` + +Verarbeitet: `PhotoUpload → SpecsResearch → JsonCoding → Validation → DraftArticle → EbayText`, `PxeInventory` + +Retries: max. 3, 2 s × 2 Backoff. Nach 3 Fehlern → `failed`-Transport. + +### `worker-orders` — Bestellungs-Worker + +```bash +php bin/console messenger:consume orders --time-limit=3600 --memory-limit=256M +``` + +Verarbeitet: `OrderReceived` (idempotency → stock lock → customer → Frappe invoice → PDF → SMTP → channel sync) + +Retries: max. 5, 1 s × 2 Backoff. + +### `worker-channel` — Channel-Sync-Worker + +```bash +php bin/console messenger:consume channel_sync --time-limit=3600 --memory-limit=256M +``` + +Verarbeitet: `PublishToChannel`, `UpdateStockOnChannels`, `DeactivateListing`, `TrackingPush` + +Retries: max. 5, 2 s × 2 Backoff, max. 60 s. Kritisch: Listing-Deaktivierung nach Verkauf. + +### `cron` — Tägliche Jobs + +```bash +sh -c "while true; do php bin/console app:logs:rotate; sleep 86400; done" +``` + +Rotiert Logs täglich: Einträge > 90 Tage mit Level > DEBUG → `logs_archive`, dann löschen. + +Kein echter Cron-Daemon im Container — simpler Shell-Loop reicht für tägliche Aufgaben. + +--- + +## Image / Dockerfile + +`docker/app/Dockerfile` — gleiches Image für `app`, alle `worker-*` und `cron`: + +``` +php:8.4-fpm-alpine + + postgresql-dev, icu-dev, libzip-dev, git, unzip + + Extensions: pdo_pgsql, intl, zip, opcache + + PECL: redis + + Composer 2 +WORKDIR /var/www +``` + +Alle Services mounten das Projekt-Verzeichnis als Bind-Mount (`.:/var/www`), kein `COPY .` im Dockerfile. Das bedeutet: Code-Änderungen ohne Rebuild wirksam — nur Cache leeren (`cache:clear`). + +--- + +## PHP-Konfiguration + +`docker/app/php.ini`: +```ini +opcache.enable=0 ; dev/CI: kein OPcache (sofort aktuelle Dateien) +memory_limit=256M +upload_max_filesize=20M +post_max_size=20M +``` + +> In Prod OPcache aktivieren (`opcache.enable=1`, `opcache.validate_timestamps=0`) für bessere Performance. + +`docker/app/zz-fpm-pool.conf`: +```ini +pm = dynamic +pm.max_children = 30 ; SSE-Verbindungen (Pipeline-Stream) halten Worker bis 90 s — +pm.start_servers = 4 ; Pool groß genug damit normale Requests nicht verhungern +pm.min_spare_servers = 2 +pm.max_spare_servers = 8 +``` + +--- + +## Volumes + +| Volume | Inhalt | Gelöscht bei | +|---|---|---| +| `postgres_data` | DB-Daten | `docker compose down -v` | +| `redis_data` | Queue-Streams, Symfony-Cache | `docker compose down -v` | +| `caddy_data` | TLS-Zertifikate (Let's Encrypt) | `docker compose down -v` | +| `.:/var/www` (Bind) | Code + `var/` (Cache, Logs, Uploads) | nie automatisch | + +**Wichtig:** `var/uploads/` enthält Artikel-Fotos und Rechnungs-PDFs. Separat sichern — liegt nicht in einem named Volume, sondern im Bind-Mount. + +--- + +## Netzwerk + +Alle Services im selben Bridge-Network `172.18.0.0/24` (Gateway `172.18.0.1`). + +- `172.18.0.1` = Host-Maschine (für externe Dienste wie Frappe ERP über `erpstaging.*`) +- Services kommunizieren über Hostname (z. B. `app:9000`, `postgres:5432`, `redis:6379`) +- Nach außen geöffnet: nur Port 80/443 via Caddy + +--- + +## Lokale Entwicklung (`docker-compose.override.yml`) + +Im Override werden PostgreSQL und Redis zusätzlich nach außen exponiert: + +```yaml +postgres: + ports: + - "5432:5432" +redis: + ports: + - "6379:6379" +``` + +Damit sind direkte Verbindungen von der IDE (TablePlus, redis-cli) möglich. **Nicht in Prod** — auf dem Server existiert nur `docker-compose.yml`. + +--- + +## Startup-Reihenfolge + +``` +redis (healthcheck) ──┐ + ├── app (FPM) +postgres (healthcheck)┘ │ + ▼ + caddy + worker-ai + worker-orders + worker-channel + cron +``` + +Worker und Cron haben kein Healthcheck auf `app` (sie brauchen nur DB + Redis direkt). + +--- + +## Wichtige Betriebsbefehle + +```bash +# Status aller Services +docker compose ps + +# Logs eines Workers in Echtzeit +docker compose logs -f worker-ai + +# Alle Logs (alle Services) +docker compose logs -f + +# Fehlgeschlagene Messenger-Jobs anzeigen +docker compose exec app php bin/console messenger:failed:show + +# Fehlgeschlagene Jobs wiederholen +docker compose exec app php bin/console messenger:failed:retry + +# Migrations ausführen +docker compose exec app php bin/console doctrine:migrations:migrate --no-interaction + +# Symfony-Cache leeren (nach Code-Änderungen) +docker compose exec app php bin/console cache:clear + +# Container neu starten (z. B. nach config/services.yaml-Änderungen) +docker compose restart app worker-ai worker-orders worker-channel + +# Kompletter Neustart (behält Volumes) +docker compose down && docker compose up -d +``` + +--- + +## Gitea Actions CI + +`.gitea/workflows/ci.yml` — läuft auf jedem Push: + +1. PHP 8.4-Alpine Container + PostgreSQL 17 + Redis 7 als Service-Container +2. `composer install` +3. PHP CS Fixer (dry-run) — schlägt fehl bei Stil-Verletzungen +4. PHPStan level 9 — schlägt fehl bei Typ-Fehlern +5. Doctrine Migrations (gegen Test-DB) +6. PHPUnit/Pest Unit + Integration Tests + +CI läuft ohne eBay/Mistral/Frappe-Credentials — alle Integrationstests, die externe APIs benötigen, überspringen sich selbst via `markTestSkipped`.