Files
desfoto/docs/deployment.md
opencode 2ccb39a69b fix: redirect plain-http desfoto.de to HTTPS with an own router
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.
2026-09-19 16:04:32 +02:00

6.7 KiB
Raw Blame History

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

/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:

curl -sS -o /dev/null -w '%{http_code} %{redirect_url}\n' http://desfoto.de/
# erwartet: 301 https://desfoto.de/

Rollback

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

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.