Files
hetzner-api-dyndns-records-…/PRODUCTION.md
T
admin df7516e7ab
Container-Image veröffentlichen / container (push) Has been cancelled
feat: production-ready domain control dashboard
2026-07-12 20:56:45 +02:00

171 lines
5.8 KiB
Markdown

# Produktivbetrieb mit Gitea und Docker Compose
Dieses Repository veröffentlicht versionierte Container-Images unter:
```text
git.j-mahler.de/admin/domain-control-webinterface-prod:vX.Y.Z
```
Das Image enthält ausschließlich die Anwendung. SQLite-Datenbank und Master-Key liegen dauerhaft auf dem Produktionsserver und bleiben bei Image-Updates erhalten.
## 1. Gitea für Releases vorbereiten
In den Repository-Einstellungen unter **Actions → Secrets** diese zwei Secrets anlegen:
- `REGISTRY_USERNAME`: Gitea-Benutzer oder eigener Release-Bot
- `REGISTRY_TOKEN`: persönlicher Gitea-Token mit Schreibrecht für Container-Pakete
Auf der Gitea-Instanz muss ein Actions-Runner mit dem Label `ubuntu-latest` registriert sein. Jeder Git-Tag, der mit `v` beginnt, startet anschließend `.gitea/workflows/release.yml` und baut Images für AMD64 und ARM64.
Erstes Release veröffentlichen:
```bash
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
```
Nach erfolgreicher Action stehen `v1.0.0` und der bewegliche Tag `stable` in der Gitea Container Registry. Auf Produktion immer den unveränderlichen Versionstag verwenden.
### Image alternativ manuell veröffentlichen
Falls Gitea Actions noch nicht eingerichtet ist:
```bash
echo "$GITEA_TOKEN" | docker login git.j-mahler.de --username DEIN_BENUTZER --password-stdin
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t git.j-mahler.de/admin/domain-control-webinterface-prod:v1.0.0 \
-t git.j-mahler.de/admin/domain-control-webinterface-prod:stable \
--push .
```
## 2. Produktionsserver vorbereiten
Voraussetzungen:
- Linux-Server mit Docker Engine und Docker-Compose-Plugin
- HTTPS-Reverse-Proxy wie Caddy, nginx oder Traefik
- DNS-Name, der auf den Server zeigt
Repository installieren:
```bash
sudo mkdir -p /opt/domain-control
sudo chown "$USER":"$USER" /opt/domain-control
git clone https://git.j-mahler.de/admin/domain-control-webinterface-prod.git /opt/domain-control
cd /opt/domain-control
cp .env.production.example .env.production
mkdir -p secrets backups
chmod 700 secrets backups
openssl rand -base64 32 > secrets/dyndns_master_key
chmod 644 secrets/dyndns_master_key
```
Der Master-Key wird vom unprivilegierten Benutzer im Container gelesen. Deshalb ist die Datei lesbar, während das übergeordnete Verzeichnis auf dem Host nur für seinen Besitzer zugänglich bleibt. Den Schlüssel niemals nachträglich ersetzen: Ohne ihn lassen sich gespeicherte Hetzner-Tokens nicht mehr entschlüsseln.
Bei einer privaten Registry auf dem Produktionsserver einmalig mit einem Token anmelden, der nur Leserechte auf Container-Pakete besitzt:
```bash
echo "$GITEA_PULL_TOKEN" | docker login git.j-mahler.de --username deploy-bot --password-stdin
```
In `.env.production` mindestens `IMAGE_TAG` kontrollieren:
```dotenv
DYNDNS_IMAGE=git.j-mahler.de/admin/domain-control-webinterface-prod
IMAGE_TAG=v1.0.0
BIND_ADDRESS=127.0.0.1
WEB_PORT=8080
APP_MODE=https
MASTER_KEY_FILE=./secrets/dyndns_master_key
```
Anwendung laden und starten:
```bash
docker compose --env-file .env.production -f compose.production.yml pull
docker compose --env-file .env.production -f compose.production.yml up -d
docker compose --env-file .env.production -f compose.production.yml ps
curl --fail http://127.0.0.1:8080/healthz
```
## 3. HTTPS-Reverse-Proxy
Port 8080 bleibt mit `BIND_ADDRESS=127.0.0.1` ausschließlich lokal erreichbar. Ein minimales Caddy-Beispiel:
```caddyfile
dns.example.de {
reverse_proxy 127.0.0.1:8080
}
```
Danach `https://dns.example.de` öffnen und den ersten Administrator anlegen. `APP_MODE=https` sorgt dabei für HTTPS-gebundene Session-Cookies.
Nur in einem isolierten, vertrauenswürdigen LAN darf stattdessen eine private Server-IP und `APP_MODE=lan` verwendet werden. Den Dienst nicht unverschlüsselt über `0.0.0.0` ins Internet stellen.
## 4. Backup vor jedem Update
Für eine konsistente SQLite-Kopie wird der Container kurz gestoppt:
```bash
cd /opt/domain-control
docker compose --env-file .env.production -f compose.production.yml stop dyndns
docker run --rm \
-v domain-control-data:/data:ro \
-v "$PWD/backups:/backup" \
alpine:3.23.2 \
cp /data/dyndns.db /backup/dyndns-before-update.db
docker compose --env-file .env.production -f compose.production.yml start dyndns
```
Zusätzlich `secrets/dyndns_master_key` verschlüsselt und außerhalb dieses Servers sichern. Eine Datenbanksicherung ohne den dazugehörigen Master-Key ist für gespeicherte API-Tokens unbrauchbar.
## 5. Update
Nach Veröffentlichung beispielsweise von `v1.1.0`:
1. Backup wie oben erstellen.
2. In `.env.production` setzen:
```dotenv
IMAGE_TAG=v1.1.0
```
3. Neues Image laden und Container ersetzen:
```bash
docker compose --env-file .env.production -f compose.production.yml pull
docker compose --env-file .env.production -f compose.production.yml up -d
docker compose --env-file .env.production -f compose.production.yml ps
docker compose --env-file .env.production -f compose.production.yml logs --tail=100
curl --fail http://127.0.0.1:8080/healthz
```
Das benannte Volume `domain-control-data` wird dabei nicht ersetzt.
## 6. Rollback
Ohne Datenbankmigration reicht es, in `.env.production` den vorherigen `IMAGE_TAG` einzutragen und erneut `pull` sowie `up -d` auszuführen.
Wenn die neue Version eine Datenbankmigration ausgeführt hat, gemeinsam auf das vorherige Image und die unmittelbar davor erstellte Datenbanksicherung zurückgehen. Automatische, unbeaufsichtigte Updates sind deshalb nicht empfohlen.
## 7. Nützliche Betriebsbefehle
```bash
# Status
docker compose --env-file .env.production -f compose.production.yml ps
# Logs
docker compose --env-file .env.production -f compose.production.yml logs -f --tail=100
# Neustart
docker compose --env-file .env.production -f compose.production.yml restart dyndns
# Bereits heruntergeladene, ungenutzte Images aufräumen
docker image prune
```