Files
vogelalbum/deploy/COOLIFY.md
T
christian cc9e0786c2 Vogelalbum 1.0.0
Eine Kamera am Vogelhaus, ein Rechner der erkennt wer da war — und eine
Sammlung, die sich von allein fuellt.

Zwei neuronale Netze: eines findet den Vogel im Bild, eines bestimmt die
Art. 148 heimische Arten, auf Deutschland eingeschraenkt und neu normiert —
ohne diese Einschraenkung wird aus einer Kohlmeise eine nordamerikanische
Schwesterart. Daraus entsteht ein Sammelalbum: nummerierte Seiten,
gefundene Arten in Farbe, fehlende als Schatten, dazu Erstfang, Punkte,
Stufen und 15 Abzeichen.

Laeuft vollstaendig auf eigener Hardware. Kein Cloud-Dienst, kein Konto,
keine Telemetrie; die Bilder verlassen das eigene Netz nicht.

Zwei getrennt deploybare Teile: die Sammlung als Django-Anwendung, die
Erkennung als eigener Dienst. Der Erkennungs-Dienst fragt selbst an und
braucht deshalb weder eine erreichbare Adresse noch einen offenen Port —
er laeuft hinter einem gewoehnlichen Router.

Dabei: Firmware fuer Raspberry Pi und ESP32-S3, Anbindung an Home
Assistant ueber MQTT, Oberflaeche auf Deutsch und Englisch, Einstellungen
je Kamera aus dem Browser, und ein Weg fuer Fotos von unterwegs.

AGPL-3.0.
2026-10-14 18:55:23 +00:00

7.0 KiB

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

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:

# 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

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.