feat(deploy): Standalone-Compose mit Host-Netzwerk für LXC und Traefik auf anderem Server

- docker-compose.standalone.yml: Build und Betrieb im Host-Netzwerk (Docker in unprivilegiertem
  LXC hat kein NAT für Bridge-Netze), Dienste über 127.0.0.1, Postgres nur lokal
- TRUST_PROXY akzeptiert die IP(s) des Reverse Proxys statt nur true/false
- Doku: Traefik-Datei-Konfiguration, Proxmox-Firewall, Ressourcen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
lucashahmannandClaude Opus 5.5 committed 2026-10-13 10:53:06 +02:00
1 parent d45d1821df
commit 9cb9531e78
5 files changed
+209 -6

No files matched your search

+79 -1
View File
@@ -42,7 +42,85 @@ Whitelist für den Proxy (alle Verbindungen sind `CONNECT …:443`):
Der SFTP-Abgleich zum Gameserver ist eine direkte SSH-Verbindung und läuft nicht über den Proxy; dafür braucht
der DMZ-Server eine eigene Firewall-Freigabe (siehe OPNsense).
## 2. Portal installieren
## Welche Compose-Datei?
| Aufbau | Datei |
|---|---|
| Traefik läuft auf **demselben** Docker-Host und findet die Dienste über Labels | `deploy/docker-compose.yml` (Abschnitte 2 ff.) |
| Traefik läuft auf **einem anderen** Server, oder Docker läuft in einem Proxmox-LXC-Container | `deploy/docker-compose.standalone.yml` (Abschnitt 2a) |
## 2a. Eigenständiger Server, z. B. Proxmox-LXC, Traefik auf einem anderen Server
In unprivilegierten LXC-Containern richtet Docker oft kein NAT für seine Bridge-Netze ein: Container erreichen dann
weder DNS noch Proxy noch das Internet (Build bricht mit „npm error Exit handler never called!“ ab). Die
Standalone-Variante nutzt deshalb für Build und Betrieb das Host-Netzwerk des LXC-Containers.
1. **Ressourcen:** Für den ersten Build 4 GB RAM einplanen (Next.js-Build und drei parallele `npm ci`), im Betrieb
reichen 2 GB.
2. **Konfiguration:**
```bash
cp deploy/.env.example deploy/.env
nano deploy/.env
```
- `TRUST_PROXY` = IP des Traefik-Servers (nur er darf die Client-IP weitergeben)
- `INGEST_HMAC_SECRET`, `POSTGRES_PASSWORD` (nur Buchstaben und Ziffern, das Passwort steht in einer URL)
- bei Bedarf `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY` (siehe „Ausgehender Proxy“)
- `DOMAIN`, `TRAEFIK_*` und `INGEST_ALLOWED_IPS` werden in dieser Variante nicht genutzt (das regelt Traefik selbst, siehe unten)
3. **Starten:**
```bash
docker compose -f deploy/docker-compose.standalone.yml --env-file deploy/.env up -d --build
docker compose -f deploy/docker-compose.standalone.yml --env-file deploy/.env ps # api, web, db: healthy
docker compose -f deploy/docker-compose.standalone.yml --env-file deploy/.env exec api node dist/cli.js admin:create <name>
docker compose -f deploy/docker-compose.standalone.yml --env-file deploy/.env exec api node dist/cli.js token:create
```
Die Dienste lauschen auf dem LXC-Container: Web auf Port **3000**, API auf Port **3001**. PostgreSQL lauscht nur
auf `127.0.0.1:5432`. Die Ports 3000, 3001 und 5432 müssen frei sein.
4. **Proxmox-Firewall** (die Netzwerkkarte hat `firewall=1`): Ports 3000 und 3001 eingehend **nur vom
Traefik-Server** erlauben. Wer die API direkt erreicht, umgeht die IP-Allowlist für `/api/ingest`. Tokens und
Signatur schützen den Datenempfang zwar weiterhin, aber die Allowlist ist eine zusätzliche Hürde.
5. **Traefik** (auf dem anderen Server) als dynamische Datei-Konfiguration, z. B. `atm10-stats.yml`:
```yaml
http:
routers:
atm-ingest:
rule: "Host(`stats.example.de`) && PathPrefix(`/api/ingest`)"
priority: 200
entryPoints: [websecure]
middlewares: [atm-ingest-allow]
service: atm-api
tls: { certResolver: letsencrypt }
atm-api:
rule: "Host(`stats.example.de`) && PathPrefix(`/api`)"
priority: 100
entryPoints: [websecure]
service: atm-api
tls: { certResolver: letsencrypt }
atm-web:
rule: "Host(`stats.example.de`)"
priority: 10
entryPoints: [websecure]
service: atm-web
tls: { certResolver: letsencrypt }
middlewares:
atm-ingest-allow:
ipAllowList: # Traefik v2: ipWhiteList
sourceRange: ["203.0.113.10/32"] # öffentliche IP des Mine-Hoster-Gameservers
services:
atm-api:
loadBalancer:
servers: [{ url: "http://<LXC-IP>:3001" }]
atm-web:
loadBalancer:
servers: [{ url: "http://<LXC-IP>:3000" }]
```
Keine Kompressions- oder Buffering-Middleware auf `/api/live` (Live-Updates per Server-Sent Events).
Steht OPNsense vor Traefik, muss Traefik die echte Client-IP sehen, sonst greift die Allowlist nicht
(ggf. `forwardedHeaders.trustedIPs` am Entrypoint).
6. Weiter mit Abschnitt 3 (Mod) bzw. 4 (SFTP). Alle späteren Befehle mit `-f deploy/docker-compose.standalone.yml`.
## 2. Portal installieren (Traefik auf demselben Docker-Host)
```bash
git clone <repo> atm10-stats && cd atm10-stats