Files
Kontrollverlust/notes/deploy.md
T
irrlicht 50be342899 Deployment-Härtung + Podman-Container
- http.Server mit Timeouts (Slowloris) und Graceful Shutdown (SIGTERM)
- Adresse und DB-Pfad per Env (KVER_ADDR, KVER_DB)
- Security-Header global: nosniff, X-Frame-Options, Referrer-Policy, CSP
- Upload-Requests hart auf 17 MB gedeckelt (MaxBytesReader statt nur
  Multipart-Speichergrenze)
- Indizes für Feed-, Thread- und Vote-Queries
- middleware.RealIP für Logging/Rate-Limit hinter dem Reverse-Proxy
- Containerfile (Multi-Stage, Alpine, non-root) + Deploy-Doku in notes/

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 07:00:09 +02:00

3.1 KiB

Deployment mit Podman

Bauen

podman build -t kver .

Multi-Stage-Build (Containerfile): statisches Go-Binary (CGO aus, modernc- SQLite ist pures Go), Laufzeit-Image ist Alpine mit unprivilegiertem Nutzer.

Starten

podman run -d --name kver \
  -p 127.0.0.1:8080:8080 \
  -v kver-data:/app/data \
  -v kver-media:/app/static/media \
  kver
  • kver-data hält die SQLite-DB (/app/data/kver.db), kver-media die hochgeladenen Bilder. Beides Named Volumes -> überleben Image-Updates.
  • Port nur an localhost binden; nach außen geht es über den Reverse-Proxy.

Konfiguration über Umgebungsvariablen:

Variable Default Bedeutung
KVER_ADDR :8080 Listen-Adresse
KVER_DB /app/data/kver.db Pfad zur SQLite-Datei

Bestehende Daten übernehmen

DB und Medien einmalig in die Volumes kopieren (Container muss dafür kurz gelaufen sein bzw. die Volumes existieren):

podman volume create kver-data
podman volume create kver-media
podman cp kver.db kver:/app/data/kver.db        # bei laufendem Container, oder:
cp kver.db "$(podman volume inspect kver-data -f '{{.Mountpoint}}')/"
cp static/media/* "$(podman volume inspect kver-media -f '{{.Mountpoint}}')/"

Danach Container neu starten. (Rootless: podman unshare cp ..., damit die UID-Zuordnung ins User-Namespace-Mapping passt.)

Reverse-Proxy (TLS)

Der Container spricht nur HTTP. Davor gehört ein TLS-terminierender Proxy (Caddy/nginx), der X-Forwarded-Proto: https setzt — daran erkennt die App HTTPS und setzt das Secure-Flag auf den Session-Cookie (siehe isHTTPS).

nginx-Beispiel:

location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $remote_addr;
    client_max_body_size 20m;   # Uploads bis 17 MB durchlassen
}

client_max_body_size muss über dem App-Limit (17 MB, maxUploadBytes) liegen, sonst kappt nginx vor der App.

Hinweis Rate-Limiting: httprate.LimitByIP auf den Auth-Routen sieht hinter einem Proxy die Proxy-IP. Entweder den Proxy die echte Client-IP als X-Real-IP/X-Forwarded-For setzen lassen und chi's middleware.RealIP vorschalten — oder damit leben, dass das Limit global statt pro IP wirkt.

Autostart mit systemd (Quadlet)

~/.config/containers/systemd/kver.container (rootless):

[Unit]
Description=kver (Kontrollverlust)

[Container]
Image=localhost/kver:latest
PublishPort=127.0.0.1:8080:8080
Volume=kver-data:/app/data
Volume=kver-media:/app/static/media

[Service]
Restart=on-failure

[Install]
WantedBy=default.target

Aktivieren:

systemctl --user daemon-reload
systemctl --user start kver
loginctl enable-linger "$USER"   # damit der Dienst ohne Login läuft

Update einspielen

podman build -t kver .
systemctl --user restart kver    # bzw. podman stop kver && podman run ...

Die App fährt bei SIGTERM sauber herunter (laufende Requests werden zu Ende bedient, WAL-Checkpoint beim DB-Close).