# Die Web-App auf Coolify stellen Für den Umzug der Sammlung von einem Rechner im Heimnetz auf eine öffentlich erreichbare Instanz. Der Detector zieht **nicht** mit um — er braucht Sichtverbindung zur Kamera und bleibt zu Hause; er bekommt am Ende nur eine neue Adresse. Vorlage ist `docker-compose.yml` im Wurzelverzeichnis, als **Docker-Compose-Anwendung aus dem Git-Repository**. ## Was dieses Setup von einem Ein-Container-Deploy unterscheidet Zwei Dienste, und der Eingang ist **nicht** die Django-App: ``` Traefik ──▶ medien (nginx, Port 8080) ──┬──▶ /media/… direkt vom Volume └──▶ alles andere ▶ web (Gunicorn, 8000) ``` 🔑 **Die Domain muss auf `medien` zeigen, nicht auf `web`.** Django liefert unter `DJANGO_DEBUG=0` keine Medien aus, und Coolify stellt keinen Webserver davor, der das übernehmen könnte — Traefik routet auf einen Container und kennt das Volume nicht. Zeigt die Domain auf `web`, läuft die Seite scheinbar normal, aber **jedes Vogelfoto ist 404**: die Sammlung ist das Einzige, was dieses Projekt ist, und sie wäre leer. Die ausführliche Begründung steht in `nginx-media.conf`. ## 1. Anwendung anlegen * Typ **Docker Compose**, Quelle das Git-Repository, Branch `main`, Compose-Datei `docker-compose.yml`. * Ist das Repository privat, muss Coolify über die Gitea-Anbindung (GitHub-App-Äquivalent bzw. Deploy-Key) darauf zugreifen können — dasselbe Verfahren wie bei den anderen Projekten auf derselben Instanz. ## 2. Domain auf den Dienst `medien` In Coolify hat bei einer Compose-Anwendung **jeder Dienst sein eigenes Domain-Feld**. Die Domain gehört an `medien` (Port 8080). `web` bekommt **keine** Domain — der Dienst wird nur intern angesprochen. Danach prüfen, dass der DNS-Eintrag der Domain auf die Coolify-Maschine zeigt. ## 3. Umgebungsvariablen Pflicht: | Variable | Wert | |---|---| | `DJANGO_ALLOWED_HOSTS` | die Domain, z. B. `vogelalbum.gaertner.onl` | | `DJANGO_CSRF_TRUSTED_ORIGINS` | dieselbe Domain **mit** `https://` | Optional, aber meist gewollt: | Variable | Wert | |---|---| | `DJANGO_SECRET_KEY` | leer lassen ist in Ordnung — der Entrypoint erzeugt einen und legt ihn aufs Volume, er bleibt über Neustarts stabil | | `VOGELALBUM_INGEST_TOKEN` | **leer lassen**, wenn jede Kamera im Admin registriert ist und ihren eigenen Token hat. Leer schaltet den Sammel-Token ab, und das ist für eine öffentliche Instanz das Richtige | | `VOGELALBUM_LESE_TOKEN` | nur nötig, wenn Home Assistant `/api/zustand/` abfragt. **Leer heißt: der Endpunkt antwortet mit 401** — wer die Einbindung zu Hause schon hat und das hier vergisst, sucht den Fehler im Heimnetz | Nicht anfassen, die stehen fest in `docker-compose.yml`: `DJANGO_DEBUG=0`, `VOGELALBUM_LIVE_ERLAUBT=0`, `SQLITE_DIR=/data`, `MEDIA_ROOT=/data/media`. ⚠️ **Lässt man Coolify ein Geheimnis erzeugen, darf es kein `$` enthalten.** Docker Compose liest `$xxx` als Variable und zerstört den Wert dabei still. Selbst erzeugen mit `openssl rand -base64 32`. 🔒 `VOGELALBUM_LIVE_ERLAUBT=0` ist der Grund, warum auf der öffentlichen Instanz kein Livebild einschaltbar ist — auch nicht mit einem Fehlklick im Admin. Ein Bild aus dem eigenen Garten soll nicht an einem Häkchen hängen. ## 4. Volume Das Compose-File deklariert das benannte Volume `vogelalbum-data` und hängt es in beide Dienste (`web` beschreibbar, `medien` nur lesend). Coolify übernimmt das; es muss nichts von Hand angelegt werden. ⚠️ **Dort liegen Datenbank *und* Fotos.** `SQLITE_DIR` und `MEDIA_ROOT` zeigen beide auf `/data`. Fehlt das Volume oder zeigt eine der beiden Variablen woandershin, ist die Sammlung nach jedem Deploy weg. ## 5. Erster Start Der Entrypoint erledigt `migrate`, `collectstatic`, `seed_dex` und holt anschließend die Referenzfotos der Arten von Wikipedia. ⏱️ **Der erste Start dauert deshalb mehrere Minuten** — rund 150 Arten, und Wikimedia drosselt, weshalb zwischen den Abrufen gewartet wird. Das ist kein hängender Deploy. Jeder weitere Start überspringt alle Arten, die schon ein Foto haben, und kostet keine einzige Anfrage mehr. Danach steht eine leere Sammlung: Artenkatalog vollständig, keine Sichtungen. ## 6. Eine bestehende Sammlung mitnehmen Wer von einer Heiminstanz umzieht, bringt Datenbank und Fotos mit. Beides liegt beim Umzugspaket im selben Layout, in dem es auf dem Volume liegen muss: ``` db.sqlite3 media/sightings/… media/crops/… media/referenz/… ``` 🔑 **Eine 1:1-Kopie, keine Neueinlieferung über die API.** Nur so kommen die Kamera-Tokens, die Schwellwerte je Kamera, Handbestimmungen, Abzeichen und Erstfang-Vermerke unverändert mit. Die Kameras zu Hause liefern danach mit **ihrem bisherigen Token** weiter — es muss nichts neu registriert werden. Auf der Heiminstanz ein konsistentes Paket bauen (`VACUUM INTO` kopiert die Datenbank sauber, auch während die App läuft): ```sh cd /pfad/zum/vogelalbum python - <<'PY' import sqlite3 sqlite3.connect('web/db.sqlite3').execute("VACUUM INTO '/tmp/db.sqlite3'") PY mkdir -p /tmp/umzug/media mv /tmp/db.sqlite3 /tmp/umzug/ cp -r web/media/sightings web/media/crops web/media/referenz /tmp/umzug/media/ tar czf /tmp/vogelalbum-daten.tgz -C /tmp/umzug db.sqlite3 media ``` Das Paket auf die Coolify-Maschine legen, dann dort: ```sh # Anwendung anhalten (in Coolify: Stop) — SQLite nicht im Betrieb tauschen. VOL=$(docker volume ls -q | grep vogelalbum-data | head -1) docker run --rm -v "$VOL":/data -v /root:/rein:ro alpine \ sh -c 'tar xzf /rein/vogelalbum-daten.tgz -C /data && chown -R 0:0 /data && ls -la /data' # Anwendung wieder starten. ``` Der Container läuft als `root`, daher `chown -R 0:0`. ## 7. Nach dem Umzug prüfen ```sh curl -sI https://DEINE-DOMAIN/ | head -1 # 200 curl -sI https://DEINE-DOMAIN/sichtungen/ | head -1 ``` Und im Browser: **eine Sichtungskarte muss ihr Foto zeigen.** Ein 404 auf `/media/…` heißt, die Domain hängt am falschen Dienst (Schritt 2). ## 8. Die Kameras zu Hause umstellen Jeder Detector und jede weitere Anbindung zeigt noch auf die alte Adresse im Heimnetz. Umzustellen ist: * **jeder Detector**: `VOGELALBUM_API` auf `https://DEINE-DOMAIN` (in `~/.vogelalbum.env` bzw. `--api`), danach neu starten. Laufen zwei Kameras, gibt es zwei Detector-Prozesse — womöglich auf zwei verschiedenen Maschinen. * **Home Assistant**, falls eingebunden: neue Adresse und `VOGELALBUM_LESE_TOKEN`. Dass eine Kamera angekommen ist, sieht man im Admin an `letzter_kontakt` — der Konfigurationsabruf setzt es, auch ohne einen einzigen Vogel. ## 9. Was bewusst nicht mit umzieht * **Der Detector.** Er braucht die Modelle, Rechenzeit und das Kameranetz. * **Das Livebild.** `VOGELALBUM_LIVE_ERLAUBT=0`, siehe oben. * **Der Störungswächter** (`vogelalbum-stoerungen.service`). 🔑 Er darf nicht auf derselben Maschine laufen wie das, was er überwacht, und nicht an derselben Stromversorgung: beim Stromausfall am 30.09.2026 war er von 22:31 bis 06:19 stumm, weil er selbst tot war. Wer das Ausbleiben von Sichtungen bemerken soll, muss von außen schauen.