99cc1f0ebb
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
121 lines
3.6 KiB
Markdown
121 lines
3.6 KiB
Markdown
# Deployment mit Podman
|
|
|
|
## Bauen
|
|
|
|
```sh
|
|
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.
|
|
|
|
Die Warnung `HEALTHCHECK is not supported for OCI image format` ist erwartbar:
|
|
Podman baut OCI-Images, dort gibt es kein eingebettetes HEALTHCHECK. Der
|
|
Healthcheck kommt stattdessen aus der Quadlet-Unit (s.u.); alternativ
|
|
`podman build --format=docker` oder `podman run --health-cmd=...`.
|
|
|
|
## Starten
|
|
|
|
```sh
|
|
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):
|
|
|
|
```sh
|
|
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:
|
|
|
|
```nginx
|
|
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):
|
|
|
|
```ini
|
|
[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
|
|
# Healthcheck hier statt im Containerfile: Podman baut standardmäßig
|
|
# OCI-Images, die kennen kein HEALTHCHECK (daher die Warnung beim Build).
|
|
HealthCmd=wget -qO /dev/null http://localhost:8080/entry/feed/0
|
|
HealthInterval=30s
|
|
|
|
[Service]
|
|
Restart=on-failure
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
```
|
|
|
|
Aktivieren:
|
|
|
|
```sh
|
|
systemctl --user daemon-reload
|
|
systemctl --user start kver
|
|
loginctl enable-linger "$USER" # damit der Dienst ohne Login läuft
|
|
```
|
|
|
|
## Update einspielen
|
|
|
|
```sh
|
|
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).
|