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>
264 lines
9 KiB
Markdown
264 lines
9 KiB
Markdown
# 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`.
|