Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Docker 16.10.2026 · 9 min Lesezeit

Tandoor Recipes mit Docker Compose selbst hosten: Rezepte, Speiseplan, Backup

Tandoor Recipes verwaltet Rezepte, Speisepläne, Einkaufslisten und Vorräte auf dem eigenen Server. Die Anleitung zeigt den getesteten Betrieb von Version 2.6.15 mit Docker Compose und PostgreSQL 16, das erste Administratorkonto, ALLOWED_HOSTS, API-Tokens, Backup mit pg_dumpall und Bildarchiv samt erprobtem Restore nach Totalverlust sowie Updates und typische Fehler.

Mit KI erstellt – redaktionelle Prüfung ausstehend

WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Titelbild: Tandoor Recipes selbst hosten mit Docker Compose, Rezepten, Einkaufsliste sowie Backup und Restore

Tandoor Recipes ist eine quelloffene Rezeptverwaltung mit Speiseplan, Einkaufsliste, Vorratsverwaltung und Import von Rezepten aus Tausenden Webseiten. Für Familien, Vereinsküchen oder kleine Gastronomiebetriebe ist das ein Weg, Rezepte zentral zu pflegen, ohne sie einer werbefinanzierten Plattform zu überlassen. Diese Anleitung zeigt die Installation von Tandoor 2.6.15 mit Docker Compose und PostgreSQL 16, das erste Administratorkonto, Absicherung, ein Backup von Datenbank und Bildern samt echtem Restore, Updates und typische Fehlerbilder. Alle Ausgaben stammen aus einem eigenen Testlauf.

Voraussetzungen

Tandoor ist eine Django-Anwendung mit drei Gunicorn-Workern. Im Test belegte der Anwendungscontainer nach dem Start rund 820 MB RAM, PostgreSQL etwa 76 MB.

  • Linux-Server oder VM mit 2 CPU-Kernen und 2 GB RAM, besser 4 GB, wenn weitere Dienste laufen; x86_64 oder ARM64
  • Rund 3 GB freier Speicher für Images und Datenbank, dazu Platz für Rezeptbilder, die mit der Sammlung wachsen
  • Docker Engine mit Compose-Plugin v2
  • Für den Zugriff von außen eine Subdomain und ein Reverse Proxy mit TLS
  • Ein Speicherziel außerhalb des Servers für Dump und Bildarchiv

Eckdaten im Überblick

PunktWert
Imagevabene1111/recipes:2.6.15 (auch :2.6, :2, :latest)
Port80 im Container (integrierter Nginx)
Volumes./postgresql (Datenbank), ./mediafiles (Bilder), staticfiles (wird beim Start neu erzeugt)
Pflicht-VariablenSECRET_KEY, ALLOWED_HOSTS, POSTGRES_PASSWORD plus Datenbankangaben
DatenbankPostgreSQL 16 (Alpine)
LizenzAGPL-3.0 mit Commons Clause

Schritt 1: Projektordner und Compose-Datei anlegen

Die Vorlage ist die Variante Plain aus der offiziellen Dokumentation. Einzige Änderung: Das Image ist auf 2.6.15 festgeschrieben. Die Dokumentation warnt ausdrücklich, dass es keinen Weg zurück auf eine ältere Version gibt, weil Datenbankmigrationen nicht rückgängig gemacht werden. Mit festem Tag entscheiden Sie selbst, wann ein Update kommt.

mkdir -p /opt/tandoor && cd /opt/tandoor

Legen Sie dort die compose.yaml an:

services:
  db_recipes:
    restart: always
    image: postgres:16-alpine
    volumes:
      - ./postgresql:/var/lib/postgresql/data
    env_file:
      - ./.env

  web_recipes:
    restart: always
    image: vabene1111/recipes:2.6.15
    env_file:
      - ./.env
    ports:
      - 80:80
    volumes:
      - staticfiles:/opt/recipes/staticfiles
      - ./mediafiles:/opt/recipes/mediafiles
    depends_on:
      - db_recipes

volumes:
  staticfiles:

Datenbank und Bilder liegen als Ordner neben der Compose-Datei, das erleichtert das Backup. Das Volume staticfiles enthält nur CSS und JavaScript, die der Container bei jedem Start neu kopiert.

Verifizieren: docker compose config --quiet läuft ohne Ausgabe durch, sobald auch die .env existiert.

Schritt 2: Die .env ausfüllen

Die Vorlage .env.template enthält nur die Pflichtwerte. Erzeugen Sie den SECRET_KEY so, wie die Vorlage es vorschlägt, mit base64 /dev/urandom | head -c50, und ein Datenbankpasswort mit openssl rand -hex 16:

SECRET_KEY=HIER_50_ZUFALLSZEICHEN
TZ=Europe/Berlin
ALLOWED_HOSTS=rezepte.example.de
DB_ENGINE=django.db.backends.postgresql
POSTGRES_HOST=db_recipes
POSTGRES_DB=djangodb
POSTGRES_PORT=5432
POSTGRES_USER=djangouser
POSTGRES_PASSWORD=HIER_ZUFALLSWERT

ALLOWED_HOSTS ist ein Schutz von Django gegen gefälschte Host-Header. Tragen Sie alle Namen ein, unter denen Tandoor erreichbar sein soll, durch Kommas getrennt, etwa rezepte.example.de,192.168.1.20. Ein Aufruf mit einem anderen Namen endet mit 400 Bad Request. Der SECRET_KEY signiert Sitzungen; ändern Sie ihn, werden alle Benutzer abgemeldet. Schützen Sie die Datei mit chmod 600 .env.

Verifizieren: grep -c HIER_ .env liefert 0.

Schritt 3: Stack starten

docker compose up -d
docker compose logs -f web_recipes

Beim ersten Start wartet der Container auf die Datenbank, spielt alle Migrationen ein, kopiert die statischen Dateien (im Test 894 static files copied) und startet Gunicorn hinter dem integrierten Nginx. Das dauerte im Test unter einer Minute. Beenden Sie die Logansicht mit Strg+C.

curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" http://localhost/
# 302 http://localhost/setup/

Verifizieren: docker compose ps zeigt beide Dienste als Up, das Log endet mit Booting worker, und die Startseite leitet auf /setup/ weiter.

Schritt 4: Administrator anlegen und Space einrichten

Öffnen Sie http://SERVER/. Die Seite /setup/ legt das erste Konto als Superuser an und funktioniert nur, solange noch kein Benutzer existiert; danach leitet sie auf die Anmeldung um. Wählen Sie ein starkes Passwort, denn Django prüft es gegen seine Passwortregeln.

Nach der ersten Anmeldung erstellt Tandoor automatisch einen Space, einen abgeschlossenen Bereich mit eigenen Rezepten, Lebensmitteln und Einheiten. Ein Assistent fragt Name, Thema und Standardeinheit ab und bietet den Import der Open-Data-Lebensmittelliste an. Alles lässt sich später unter Einstellungen ändern.

Einrichtungsassistent von Tandoor Recipes 2.6.15 mit den Feldern Name, Thema und Standardeinheit für den neuen Space
Nach der ersten Anmeldung fragt der Assistent Name, Thema und Standardeinheit des Spaces ab.

Weitere Personen laden Sie unter Einstellungen per Einladungslink in Ihren Space ein und vergeben dabei eine Gruppe: guest darf lesen, user bearbeiten, admin verwalten. Die freie Registrierung bleibt geschlossen, solange ENABLE_SIGNUP auf dem Standardwert 0 steht. Im Test zeigte /accounts/signup/ die Meldung Sign Up Closed.

Verifizieren: Ein erneuter Aufruf von /setup/ leitet mit 302 auf /accounts/login/ um, und in der Seitenleiste steht Ihr Space mit der Version Tandoor 2.6.15.

Schritt 5: Rezepte erfassen und per API prüfen

Rezepte legen Sie über das Pluszeichen oben rechts an oder importieren sie unter Importieren per URL. Für den Test entstand ein Rezept Restore-Marker Linsencurry mit drei Zutaten, Stichwort restoretest und einem hochgeladenen Bild. Es dient später als Nachweis für den Restore.

Rezeptansicht in Tandoor mit dem Testrezept Restore-Marker Linsencurry, Zutatenliste und Zubereitungsschritt
Das Testrezept mit Zutaten, Zeiten und Zubereitungsschritt dient als Nachweis für den Restore.

Für Skripte und Apps gibt Tandoor Zugangstokens aus. Einen Token erhalten Sie über den Endpunkt /api-token-auth/; im Test galt er fünf Jahre mit dem Bereich read write app.

curl -s -X POST http://localhost/api-token-auth/ -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"IHR_PASSWORT"}'
curl -s "http://localhost/api/recipe/?query=Restore" -H "Authorization: Bearer tda_..."

Ohne Token oder mit falschem Token antwortet die API mit 403.

Verifizieren: Die Abfrage liefert das Testrezept mit ID und Namen; auf der Startseite erscheint es unter Zufällige Rezepte.

Startseite von Tandoor Recipes mit Speiseplan für vier Tage und dem Testrezept unter Zufällige Rezepte
Die Startseite zeigt den Speiseplan der nächsten Tage und Rezepte aus der Sammlung.

Schritt 6: Hinter einen Reverse Proxy stellen

Der integrierte Nginx liefert Anwendung, statische Dateien und Bilder aus, ein vorgeschalteter Proxy übernimmt nur TLS. Binden Sie den Port dann lokal und auf einen freien Port, damit kein anderer Webserver auf Port 80 kollidiert:

    ports:
      - 127.0.0.1:8080:80

In Caddy genügt rezepte.example.de { reverse_proxy 127.0.0.1:8080 }. Der Proxy muss den Host-Header unverändert weitergeben, sonst greift ALLOWED_HOSTS. Im Test lief die Oberfläche über einen HTTPS-Proxy; Seitenaufrufe, API-Anfragen und der Bild-Upload aus dem Browser funktionierten ohne weitere Einstellungen. Nutzen Sie einen Proxy mit Upload-Grenze, etwa Nginx mit 1 MB Standard, erhöhen Sie client_max_body_size für Rezeptbilder.

Verifizieren: curl -I https://rezepte.example.de/accounts/login/ liefert 200.

Schritt 7: Backup und Restore von Datenbank und Bildern

Tandoor hat keine eingebaute Sicherung. Zu sichern sind die PostgreSQL-Datenbank und der Ordner mediafiles; die Dateinamen der Bilder stehen in der Datenbank, deshalb gehören beide Teile aus demselben Zeitpunkt zusammen. Die Dokumentation empfiehlt pg_dumpall:

mkdir -p /opt/tandoor/backup
docker compose exec -T db_recipes pg_dumpall -U djangouser > backup/pgdump-$(date +%F).sql
tar czf backup/mediafiles-$(date +%F).tgz mediafiles

Im Test war der Dump rund 340 KB groß. Ein Kopieren des Ordners postgresql ist kein Ersatz, weil die Dateien nur mit derselben PostgreSQL-Hauptversion lesbar sind.

Für den Restore-Test wurde ein Totalverlust simuliert: Anwendung stoppen, Datenbank löschen, Bilderordner entfernen, dann beides zurückspielen.

docker compose stop web_recipes
docker compose exec -T db_recipes psql -U djangouser -d postgres -c 'DROP DATABASE djangodb;'
rm -rf mediafiles/recipes

docker compose exec -T db_recipes psql -U djangouser -d postgres -q -f - < backup/pgdump-2026-10-16.sql
tar xzf backup/mediafiles-2026-10-16.tgz
docker compose start web_recipes

Der Restore verbindet sich mit der Datenbank postgres, weil pg_dumpall die Datenbank djangodb selbst neu anlegt. Eine einzelne Meldung ERROR: role "djangouser" already exists ist dabei normal und harmlos: Die Rolle existiert im laufenden Container bereits. Weitere Fehlermeldungen gab es im Test nicht.

Verifizieren: Nach dem Start antwortete die API im Test nach 15 Sekunden wieder mit 200 und lieferte Restore-Marker Linsencurry samt Bildpfad. Die SHA-256-Prüfsumme des Rezeptbilds war vor und nach dem Restore identisch, das Bild wurde mit 200 ausgeliefert:

docker compose exec -T db_recipes psql -U djangouser -d djangodb -tAc "select id,name from cookbook_recipe"
# 1|Restore-Marker Linsencurry

Schritt 8: Updates einspielen

Lesen Sie vor jedem Update die Release Notes, legen Sie ein frisches Backup an und erhöhen Sie dann den Tag in der compose.yaml:

docker compose pull
docker compose up -d
docker compose logs --no-log-prefix web_recipes | grep -E "migrations|static files"

Migrationen und statische Dateien erledigt der Container beim Start. Im Test lief ein erneuter Start nach pull sauber durch, die Anmeldeseite antwortete danach mit 200. Ein Wechsel der PostgreSQL-Hauptversion, etwa von 16 auf 17, ist ein eigener Vorgang: Dump anlegen, Stack stoppen, Ordner postgresql umbenennen, neues Image starten, Dump einspielen. Danach prüfen Sie, ob die Erweiterungen pg_trgm und unaccent vorhanden sind, die Tandoor für die Suche nutzt.

Verifizieren: Die Seitenleiste zeigt die neue Versionsnummer, und select extname from pg_extension listet pg_trgm und unaccent.

Troubleshooting

400 Bad Request beim Aufruf

Der Hostname fehlt in ALLOWED_HOSTS. Das Log zeigt dann django.security.DisallowedHost Invalid HTTP_HOST header mit dem abgewiesenen Namen. Ergänzen Sie ihn und starten Sie mit docker compose up -d neu.

Fehler beim Laden mit Status 403 direkt nach der ersten Anmeldung

Im Test meldete die Oberfläche unmittelbar nach dem Anlegen des Spaces Fehler beim Laden mit Status 403, obwohl Rechte und Gruppe in der Datenbank korrekt waren. Tandoor speichert Gruppenrechte 30 Sekunden zwischen. Warten Sie kurz und laden Sie die Seite neu, dann verschwindet die Meldung.

Port 80 ist belegt

Läuft auf dem Host bereits ein Webserver, startet der Container nicht. Mappen Sie einen anderen Port, wie in Schritt 6 gezeigt.

Bilder fehlen nach einem Umzug

Datenbank und mediafiles stammen aus unterschiedlichen Zeitpunkten oder der Ordner wurde nicht mitkopiert. Spielen Sie beide Teile aus demselben Backup ein.

Häufige Fragen

Darf ich Tandoor im Betrieb einsetzen?

Ja, für die eigene Nutzung. Die Lizenz ist AGPL-3.0 mit Commons-Clause-Zusatz: Sie dürfen Tandoor nicht als kostenpflichtigen Dienst für Dritte anbieten, dessen Wert im Wesentlichen aus der Software besteht, auch nicht als bezahltes Hosting.

Kann ich mehrere Haushalte trennen?

Ja. Jeder Space ist abgeschlossen, und innerhalb eines Spaces lassen sich Haushalte für getrennte Speisepläne und Einkaufslisten anlegen.

Gibt es eine Export-Funktion als zusätzliche Sicherung?

Rezepte lassen sich in der Oberfläche als ZIP exportieren und unter Importieren wieder einlesen. Das ersetzt kein Datenbank-Backup, weil Speisepläne, Einkaufslisten und Benutzer nicht enthalten sind.

Brauche ich einen eigenen Webserver für die Bilder?

Nein. Seit Version 2 liefert der integrierte Nginx auch die Bilder aus dem Ordner mediafiles aus.

Fazit

Tandoor Recipes läuft mit zwei Containern und einer kurzen .env zuverlässig. Die wichtigsten Punkte sind ein korrektes ALLOWED_HOSTS, ein fest gepinntes Image, weil es keinen Downgrade gibt, und ein Backup aus Datenbank und Bilderordner. Der Restore aus pg_dumpall und Bildarchiv funktionierte im Test vollständig, bis hin zur identischen Prüfsumme des Rezeptbilds.

Weiterführende Anleitungen und Quellen

Tandoor RecipesRezeptverwaltungSpeiseplanDocker ComposePostgreSQLSelfhostingBackup