The running Traefik instance has no global entrypoint http->https redirection even though /srv/stack/docker-compose.yml declares one, so plain http://desfoto.de/ answered with Traefik's default 404 once the old desfoto-redirect-http router was removed. Route desfoto.de through its own http router (desfoto-http + desfoto-http-to-https, permanent redirect) via the proven live Docker provider instead of restarting the shared proxy. Document the live-config drift in docs/deployment.md.
139 lines
6.7 KiB
Markdown
139 lines
6.7 KiB
Markdown
# Deployment — desfoto.de
|
||
|
||
## Zielumgebung
|
||
|
||
| Baustein | Wert |
|
||
| --- | --- |
|
||
| Produktionshost | `prod-main` = `opencode-prod@87.106.24.138` (NOPASSWD sudo) |
|
||
| Live-Verzeichnis | `/home/denny/stacks/desfoto/` (Owner `denny:denny`) |
|
||
| Release-Snapshots | `/home/denny/stacks/desfoto-releases/<stamp>-<sha>/` |
|
||
| Compose-Dateien | `compose.yml` + `compose.vps.yml`, Projektname `desfoto` |
|
||
| Container | `desfoto-web-1` (`nginx:1.28-alpine`), gebunden an `127.0.0.1:18430` |
|
||
| Netzwerk | externes Docker-Netzwerk `web` (Traefik-Docker-Provider) |
|
||
| Reverse Proxy | Traefik v3 im Projekt `stack` (`/srv/stack/docker-compose.yml`) |
|
||
| Entrypoints | `http` und `https`, ACME-Resolver `le`, HTTP-01-Challenge |
|
||
| Zertifikate | `/srv/traefik/acme.json` |
|
||
| Domains | `desfoto.de` (kanonisch) und `www.desfoto.de` → 301 auf Apex |
|
||
|
||
Der Proxy wird **niemals** neu gestartet oder neu erzeugt. Die Anbindung erfolgt
|
||
ausschließlich über Labels in `compose.vps.yml`.
|
||
|
||
## Ablauf einer Veröffentlichung
|
||
|
||
```bash
|
||
/home/king/bin/oc-release /home/king/projects/desfoto \
|
||
"feat: ..." <geänderte Dateien...>
|
||
```
|
||
|
||
`oc-release` committet und pusht die genannten Dateien und ruft danach die
|
||
getrackten Hooks auf:
|
||
|
||
1. `.ocauto/deploy <sha>`
|
||
- prüft, dass `HEAD` dem Release entspricht und `site/`, `nginx.conf`,
|
||
`compose.yml`, `compose.vps.yml` keine uncommitteten Änderungen haben;
|
||
- packt diese Pfade in ein Archiv und lädt es nach `prod-main:/tmp`;
|
||
- legt auf dem Server zuerst einen Snapshot der aktuell laufenden Version an
|
||
(`/home/denny/stacks/desfoto-releases/<stamp>-<sha>/`, zusätzlich als
|
||
`previous` verlinkt);
|
||
- entpackt die neue Version nach `/home/denny/stacks/desfoto/`, setzt den
|
||
Owner auf `denny:denny` und schreibt die Release-Kennung nach `RELEASE`;
|
||
- `docker compose -f compose.yml -f compose.vps.yml up -d --remove-orphans`
|
||
und wartet auf `healthy`;
|
||
- entfernt einmalig die veralteten `desfoto.de`-Weiterleitungs-Labels aus
|
||
`/srv/stack/docker-compose.yml`: Es werden ausschließlich Traefik-Label-Zeilen
|
||
mit `desfoto`-Bezug sowie der zugehörige Kommentar entfernt, das Ergebnis wird
|
||
zuerst als Compose-Projekt validiert (`config --quiet`) und danach per atomarem
|
||
`mv` an die Stelle der Live-Datei gesetzt. Eine Vorher-Fassung liegt im
|
||
Snapshot (`docker-compose.yml.stack-backup`) und daneben als
|
||
`docker-compose.yml.bak.<stamp>`. Danach wird ausschließlich der
|
||
`landing`-Container mit `--no-deps` neu erzeugt, damit `dennyapp.de` und
|
||
`dennyschulz.de` unverändert weiterlaufen. Bleibt eine `desfoto`-Referenz
|
||
übrig (z. B. ein eigener Service-Block), wird sie als WARNUNG ausgegeben.
|
||
|
||
2. `.ocauto/verify <sha>`
|
||
- vergleicht den Dateibaum unter `/home/denny/stacks/desfoto/site` mit dem
|
||
committeten `site/` (SHA-256 über alle Dateien außer den datierten
|
||
`sitemap.xml`/`security.txt`);
|
||
- prüft alle 13 Routen, die 404-Seite, `robots.txt`, `sitemap.xml`,
|
||
`.well-known/security.txt`, Bilder und Schriften;
|
||
- prüft die Weiterleitungen `http → https` und `www → Apex`;
|
||
- prüft die Sicherheits-Header, dass kein `Set-Cookie` gesetzt wird und dass
|
||
das Zertifikat noch mindestens sieben Tage gültig ist;
|
||
- prüft, dass `www.dennyschulz.de` und `dennyapp.de` weiterhin erreichbar sind.
|
||
|
||
## Routing
|
||
|
||
`desfoto.de` wird vor dem Aufräumen bereits vom neuen Container bedient, weil die
|
||
Routers in `compose.vps.yml` eine explizite Traefik-Priorität `200` tragen und die
|
||
alten Weiterleitungsrouters keine Priorität setzen (damit gilt dort die
|
||
Regel-Länge). Der Container wird also zuerst gesund geprüft, und erst danach werden
|
||
die alten Labels entfernt — der Übergang hat damit kein Fenster ohne Antwort.
|
||
|
||
`www.desfoto.de` wird über die Middleware `desfoto-canonical` dauerhaft auf
|
||
`https://desfoto.de/...` umgeschrieben. Für `http` bringt die Anwendung einen
|
||
eigenen Router (`desfoto-http` + `desfoto-http-to-https`) mit, der dauerhaft
|
||
(301) auf `https` umleitet. Die ACME-HTTP-01-Challenge beantwortet Traefik über
|
||
den internen `acme-http`-Router mit maximaler Priorität, bevor dieser Redirect
|
||
greift.
|
||
|
||
### Abweichung der laufenden Traefik-Konfiguration (Live-Drift)
|
||
|
||
`/srv/stack/docker-compose.yml` deklariert eine globale
|
||
Entrypoint-Weiterleitung `http → https`
|
||
(`--entrypoints.http.http.redirections.entryPoint.to=https` /
|
||
`…scheme=https`, Zeilen 10–11). Der **laufende** Container `traefik` führt diese
|
||
Flags nachweislich nicht (`docker inspect traefik --format '{{json .Args}}'`
|
||
enthält keine `redirections`-Flags; `http://dennyapp.de/` leitet nur deshalb um,
|
||
weil `landing` einen eigenen `landing-http`-Router besitzt). Die Datei auf der
|
||
Platte und die laufende Konfiguration stimmen also nicht überein.
|
||
|
||
Der Proxy wird deswegen **nicht** aus der Datei neu erzeugt oder neu gestartet:
|
||
Das würde alle fremden Routen risikobehaftet anfassen. Stattdessen bringt jede
|
||
Anwendung ihr eigenes HTTP-Routing mit — `desfoto.de` über die Labels in
|
||
`compose.vps.yml`. Wird die globale Weiterleitung später bewusst aktiviert,
|
||
bleibt der eigene Router schlicht wirkungslos.
|
||
|
||
Nachzuprüfen mit:
|
||
|
||
```bash
|
||
curl -sS -o /dev/null -w '%{http_code} %{redirect_url}\n' http://desfoto.de/
|
||
# erwartet: 301 https://desfoto.de/
|
||
```
|
||
|
||
## Rollback
|
||
|
||
```bash
|
||
ROLLBACK_TO=/home/denny/stacks/desfoto-releases/<stamp>-<sha> \
|
||
/home/king/projects/desfoto/.ocauto/deploy rollback
|
||
```
|
||
|
||
Der Befehl synchronisiert `site/`, `nginx.conf` und beide Compose-Dateien aus dem
|
||
Snapshot zurück nach `/home/denny/stacks/desfoto/` und startet den Stack neu. Die
|
||
frühere Version bleibt so lange verfügbar, bis sie bewusst gelöscht wird.
|
||
|
||
Jeder Release legt den Snapshot an, auch der allererste. Enthält der Snapshot noch
|
||
kein `site/` (weil vorher nichts ausgeliefert wurde), entfernt der Rollback den
|
||
desfoto-Stack wieder und stellt den Zustand vor der Veröffentlichung her.
|
||
|
||
Wurde `/srv/stack/docker-compose.yml` verändert, liegt die Vorher-Fassung sowohl im
|
||
Snapshot als `docker-compose.yml.stack-backup` als auch daneben unter
|
||
`docker-compose.yml.bak.<stamp>`. Beide werden vom Rollback automatisch
|
||
zurückgespielt, gefolgt von `docker compose up -d --no-deps landing`; das Backup
|
||
`.bak.<stamp>` bleibt zusätzlich für einen manuellen Eingriff liegen.
|
||
|
||
## Betrieb
|
||
|
||
```bash
|
||
ssh prod-main
|
||
sudo docker compose -f /home/denny/stacks/desfoto/compose.yml \
|
||
-f /home/denny/stacks/desfoto/compose.vps.yml ps
|
||
sudo docker logs --tail 50 desfoto-web-1
|
||
sudo docker inspect --format '{{.State.Health.Status}}' desfoto-web-1
|
||
```
|
||
|
||
Access-Logs gibt es bewusst nicht (`access_log off` in `nginx.conf`). Auch Fehler
|
||
schreibt nginx nicht weg: `error_log /dev/null crit;` verwirft sie vollständig,
|
||
statt sie in eine Datei zu schreiben. Der JSON-Log-Treiber des Containers ist
|
||
zusätzlich auf 5 MB × 2 Dateien begrenzt. Das entspricht der Zusage in der
|
||
Datenschutzerklärung.
|