Skip to main content

Tandoor

A self-hosted recipe management and meal planning application.

Tandoor Recipes is an open-source recipe manager with a rich web UI for storing, organizing, and planning recipes. It supports importing from URLs, nutritional calculations, shopping list generation, and meal planning. Self-hosting keeps your recipe collection private without subscription fees.

Alternatives considered​

Cloud Hosted

ToolOpen SourceFree TierMonthly Cost
PaprikaNoNo$4.99–$29.99 one-time
AnyListNoLimited$1.25/mo (annual)

Self Hosted

ToolOpen SourceFull FeaturesNotes
MealieYesYesSimpler recipe manager with meal planning

Installation​

Architecture​

  • Deployment: Single tandoor deployment in the tandoor namespace
  • Image: vabene1111/recipes, digest-pinned via Renovate
  • Database: CNPG PostgreSQL cluster with Longhorn-encrypted PVCs
  • Storage: Two Longhorn PVCs — tandoor-media and tandoor-static
  • Networking: ClusterIP service on port 8080, HTTPRoute via internal gateway

Security​

  • No runAsUser is set, so the container starts as the image's default user, which is root; privilege escalation is blocked and all capabilities are dropped
  • Longhorn PVCs encrypted at rest via SOPS-managed keys

Updates​

Managed by Renovate. Image is digest-pinned.

Data Management​

  • Database: CNPG PostgreSQL cluster (Longhorn-encrypted PVCs)
  • PVCs: tandoor-media and tandoor-static (both Longhorn-encrypted, k8up.io/backup: "true")
  • Backups: k8up Schedules to Hetzner and Backblaze via restic, covering the media and static PVCs and the database dump from the backups-cnpg PreBackupPod.

User Management​

No OIDC env vars in manifests. User accounts managed through the Tandoor admin UI. Multi-user sharing supported natively.

Configuration Management​

  • Database credentials injected from CNPG-generated secret
  • App secret key and other credentials from SOPS-encrypted secret

Usage​

Import recipes by pasting URLs from cooking websites — Tandoor automatically extracts ingredients and steps. Organize recipes with tags and categories. Generate weekly meal plans and export shopping lists. Multiple users can share the same recipe collection.

Cluster-specific deviations from the above live in the per-cluster README — see k8s/apps/tandoor/talos/README.md.

Cluster Deployment​

Tandoor — Talos cluster

Kubernetes Metadata
  • Image: vabene1111/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72
Rendered manifests (kustomize build)
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kustomize.toolkit.fluxcd.io/force: enabled
labels:
app.kubernetes.io/name: tandoor
name: tandoor
namespace: tandoor
spec:
replicas: 1
revisionHistoryLimit: 2
selector:
matchLabels:
app.kubernetes.io/name: tandoor
strategy:
type: Recreate
template:
metadata:
labels:
app.kubernetes.io/name: tandoor
spec:
automountServiceAccountToken: false
containers:
- env:
- name: DB_ENGINE
value: django.db.backends.postgresql
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
key: password
name: cnpg-app
- name: POSTGRES_HOST
valueFrom:
secretKeyRef:
key: host
name: cnpg-app
- name: POSTGRES_DB
valueFrom:
secretKeyRef:
key: dbname
name: cnpg-app
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
key: user
name: cnpg-app
- name: POSTGRES_PORT
valueFrom:
secretKeyRef:
key: port
name: cnpg-app
- name: EMAIL_HOST
valueFrom:
secretKeyRef:
key: host
name: allinkl-smtp-credentials
- name: EMAIL_PORT
valueFrom:
secretKeyRef:
key: port
name: allinkl-smtp-credentials
- name: EMAIL_HOST_USER
valueFrom:
secretKeyRef:
key: username
name: allinkl-smtp-credentials
- name: EMAIL_HOST_PASSWORD
valueFrom:
secretKeyRef:
key: password
name: allinkl-smtp-credentials
- name: DEFAULT_FROM_EMAIL
valueFrom:
secretKeyRef:
key: from
name: allinkl-smtp-credentials
- name: EMAIL_USE_TLS
value: '1'
- name: EMAIL_USE_SSL
value: '1'
- name: SECRET_KEY
value: keepthisasecretinprod
envFrom:
- secretRef:
name: tandoor
image: vabene1111/recipes:2.6.15@sha256:2e759dd1478a2ed119ee474e28522079fb1cfa50b3fd25cba89f6b9a67abad72
livenessProbe:
failureThreshold: 5
httpGet:
path: /
port: 80
initialDelaySeconds: 30
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 2
name: tandoor
ports:
- containerPort: 80
name: web
protocol: TCP
readinessProbe:
failureThreshold: 5
httpGet:
path: /
port: 80
initialDelaySeconds: 30
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 2
resources:
limits:
memory: 2Gi
requests:
cpu: 25m
memory: 1Gi
volumeMounts:
- mountPath: /opt/recipes/staticfiles/
name: tandoor-static
- mountPath: /opt/recipes/mediafiles/
name: tandoor-media
priorityClassName: tier-2
volumes:
- name: tandoor-static
persistentVolumeClaim:
claimName: tandoor-static
- name: tandoor-media
persistentVolumeClaim:
claimName: tandoor-media