Files
vogelalbum/docker-compose.yml
christian c11fe9ab04 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-15 14:30:02 +00:00

99 lines
4.6 KiB
YAML

# Deployment der Web-App auf Coolify (vogelalbum.gaertner.onl).
#
# Der Detector läuft NICHT hier, sondern als systemd-Dienst auf dem Dev-Server:
# er braucht die Modelle, numpy und vor allem Sichtverbindung zur Kamera im
# WLAN-Segment. Siehe deploy/vogelalbum-detector.service.
#
# 🔑 Zwei Dienste, und der Eingang ist nginx, nicht Gunicorn. Grund steht
# ausführlich in deploy/nginx-media.conf: Django liefert unter DJANGO_DEBUG=0
# keine Medien aus, und Coolify stellt keinen Webserver davor, der das
# übernehmen könnte. Traefik muss deshalb auf `medien` zeigen — dort liegt
# Port 8080.
services:
web:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
environment:
DJANGO_DEBUG: "0"
# Leer ist der Normalfall: der Entrypoint erzeugt dann einen Schluessel
# und legt ihn aufs Volume, wo er ueber Neustarts stabil bleibt. Die
# Vorgabe `:-` muss sein, sonst warnt Compose bei jedem Deploy ueber eine
# nicht gesetzte Variable — eine Warnung, die nach Fehler aussieht.
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY:-}
DJANGO_ALLOWED_HOSTS: ${DJANGO_ALLOWED_HOSTS:-vogelalbum.gaertner.onl}
DJANGO_CSRF_TRUSTED_ORIGINS: ${DJANGO_CSRF_TRUSTED_ORIGINS:-https://vogelalbum.gaertner.onl}
# Der Sammel-Token für Kameras ohne eigene Registrierung. Leer lassen,
# wenn jede Kamera im Admin steht und ihren eigenen Token hat — dann ist
# dieser Weg zu, und das ist für eine öffentliche Instanz das Richtige.
# Leer ist ausdrücklich erlaubt und schaltet ihn ab (siehe dex/api.py).
VOGELALBUM_INGEST_TOKEN: ${VOGELALBUM_INGEST_TOKEN:-}
# Token zum LESEN nach außen (/api/zustand/, Home Assistant). Leer heißt
# zu. ⚠️ Wer Home Assistant angebunden hat, muss ihn hier setzen —
# sonst antwortet der Endpunkt nach dem Umzug mit 401, und die
# Einbindung zu Hause sieht aus wie ein Netzproblem.
VOGELALBUM_LESE_TOKEN: ${VOGELALBUM_LESE_TOKEN:-}
# ⚠️ Auf der öffentlichen Instanz bleibt das Livebild aus. Der Standard im
# Code ist "1" — ohne diese Zeile wäre es auf einer öffentlichen Seite
# also AN, und das Bild aus dem eigenen Garten hinge an einem Häkchen im
# Admin. Mit 0 ist Live gar nicht erst einschaltbar.
VOGELALBUM_LIVE_ERLAUBT: "0"
# Beides MUSS auf das Volume zeigen. Liegt die SQLite-Datei oder der
# Medienordner im Container-Dateisystem, sind Sammlung und Fotos nach
# jedem Deploy weg.
SQLITE_DIR: /data
MEDIA_ROOT: /data/media
volumes:
- vogelalbum-data:/data
# 🔑 Hier steht bewusst KEIN `ports` und KEIN `expose`. Coolify bietet für
# jeden Dienst, der eines von beidem hat, ein Domain-Feld an — und fragt
# dann nach einer Domain für `web`. Wer dort eine eintraegt, hat einen
# zweiten Eingang, der nginx umgeht: die Seite laedt, aber jedes Vogelfoto
# ist 404. Die Domain gehoert ausschliesslich an `medien`.
#
# Dass `medien` die App trotzdem erreicht, aendert das nicht: Container im
# selben Compose-Netz sprechen sich auf jedem Port an. `expose` ist reine
# Dokumentation und oeffnet nichts, was sonst zu waere.
medien:
# Die Konfiguration steckt im Image, nicht in einem Mount — Begründung
# steht in deploy/Dockerfile.nginx.
build:
context: .
dockerfile: deploy/Dockerfile.nginx
restart: unless-stopped
depends_on:
- web
volumes:
# Nur lesen. Geschrieben wird der Medienordner ausschließlich von der
# Web-App, die ihn unter MEDIA_ROOT bespielt.
- vogelalbum-data:/data:ro
# `expose`, nicht `ports`: Traefik erreicht den Dienst im Compose-Netz, und
# das genuegt. Ein `ports: "8080"` wuerde den Port zusaetzlich auf einem
# zufaelligen Host-Port veroeffentlichen — ein zweiter, unverschluesselter
# Eingang an der oeffentlichen IP, den niemand braucht.
expose:
- "8080"
volumes:
# ⚠️ Ein Volume-Name ist kein Text, sondern eine ADRESSE.
#
# Wird er geaendert, legt Docker ein neues, LEERES Volume an. Die Seite
# kommt dann ohne Sammlung und ohne Fotos zurueck. Verloren ist nichts —
# die Daten liegen im alten Volume, an dem nur niemand mehr haengt —, aber
# fuer die Dauer eines Deploys sieht es aus wie Datenverlust.
#
# 🔑 Wer diesen Namen aendert, muss die Daten mitnehmen. Einmal kopieren,
# pruefen, dann erst umstellen:
#
# docker volume ls | grep <alt>
# docker stop <container>
# cp -a <alt-mountpoint>/. <neu-mountpoint>/
# diff -rq <alt> <neu>
#
# In dieselbe Klasse gehoeren MQTT-Topics und die Umgebungsvariablen
# laufender Anlagen: Adressen, keine Beschriftungen.
vogelalbum-data: