docs: add Docker layout and service interaction documentation
Some checks are pending
CI / test (push) Waiting to run
Some checks are pending
CI / test (push) Waiting to run
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 <noreply@anthropic.com>
This commit is contained in:
parent
c98f53c6e5
commit
36e7d02539
1 changed files with 264 additions and 0 deletions
264
docs/docker.md
Normal file
264
docs/docker.md
Normal file
|
|
@ -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`.
|
||||||
Loading…
Reference in a new issue