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
| Tool | Open Source | Free Tier | Monthly Cost |
|---|---|---|---|
| Paprika | No | No | $4.99–$29.99 one-time |
| AnyList | No | Limited | $1.25/mo (annual) |
Self Hosted
| Tool | Open Source | Full Features | Notes |
|---|---|---|---|
| Mealie | Yes | Yes | Simpler recipe manager with meal planning |
Installation
Architecture
- Deployment: Single
tandoordeployment in thetandoornamespace - Image:
vabene1111/recipes:2.6.0(digest-pinned) - Database: CNPG PostgreSQL cluster with Longhorn-encrypted PVCs
- Storage: Two Longhorn PVCs —
tandoor-mediaandtandoor-static - Networking: ClusterIP service on port 8080, HTTPRoute via internal gateway
Security
- Runs as
runAsUser: 0,runAsNonRoot: false - 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-mediaandtandoor-static(both Longhorn-encrypted,k8up.io/backup: "true") - Backups: k8up
Schedulebacks up CNPG Longhorn PVCs and app data to Hetzner S3. CNPG annotated withk8up.io/backupcommand: pg_dump.
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
Administration
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/talos/tandoor/README.md.
Cluster Deployment
Tandoor — Talos cluster
Cluster-specific notes only. General product info, "why we use it", and alternatives live in docusaurus/docs/apps/tandoor.mdx.
Deviations from defaults
Defaults live in docusaurus/docs/apps/tandoor.mdx — document anything this cluster does differently here, with a one-line reason.
- Image:
vabene1111/recipes:2.6.9@sha256:969c5b3552ffbf18a6f82b3ad5babbae89bfbd30ab6e3195fd3c158bcf3062ed
Rendered manifests (kustomize build)
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kustomize.toolkit.fluxcd.io/force: enabled
labels:
app.kubernetes.io/instance: tandoor
app.kubernetes.io/name: tandoor
name: tandoor
namespace: tandoor
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/instance: tandoor
app.kubernetes.io/name: tandoor
ingress: public
strategy:
type: Recreate
template:
metadata:
labels:
app.kubernetes.io/instance: tandoor
app.kubernetes.io/name: tandoor
ingress: public
spec:
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.9@sha256:969c5b3552ffbf18a6f82b3ad5babbae89bfbd30ab6e3195fd3c158bcf3062ed
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
volumeMounts:
- mountPath: /opt/recipes/staticfiles/
name: tandoor-static
- mountPath: /opt/recipes/mediafiles/
name: tandoor-media
volumes:
- name: tandoor-static
persistentVolumeClaim:
claimName: tandoor-static
- name: tandoor-media
persistentVolumeClaim:
claimName: tandoor-media