How to Self-Host Tandoor Recipes: Meal Planner and Shopping Lists 2026
TL;DR
Tandoor Recipes (MIT, ~5K GitHub stars, Python/Vue) is a feature-rich self-hosted recipe manager. It imports recipes from any URL (Allrecipes, NYT Cooking, The Guardian, etc.), tracks nutrition, plans meals on a weekly calendar, and generates shopping lists from your meal plan. Recipe services like Paprika 3 charge $4.99 one-time but sync to cloud; Whisk and Mealime use subscriptions. Tandoor is free, self-hosted, and has a strong REST API for automation.
Key Takeaways
- Tandoor: MIT, ~5K stars, Python/Vue — recipe import, nutrition, meal planning, shopping lists
- URL import: Scrapes recipe sites automatically (Allrecipes, NYT Cooking, BBC Food, etc.)
- Nutrition tracking: Automatic nutrition facts via built-in Open Food Facts integration
- Meal planner: Weekly view, drag-and-drop recipes to days, generate shopping lists
- Shopping lists: Auto-combine ingredients from multiple recipes, group by supermarket aisle
- REST API: Full API for integration with Home Assistant, Grocy, other tools
Part 1: Docker Setup
# docker-compose.yml
services:
db:
image: postgres:15-alpine
restart: unless-stopped
environment:
POSTGRES_DB: djangodb
POSTGRES_USER: djangouser
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U djangouser"]
interval: 10s
start_period: 20s
web:
image: vabene1111/recipes:latest
container_name: tandoor
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- tandoor_media:/opt/recipes/mediafiles
- tandoor_static:/opt/recipes/staticfiles
environment:
DB_ENGINE: django.db.backends.postgresql
POSTGRES_HOST: db
POSTGRES_PORT: 5432
POSTGRES_USER: djangouser
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
POSTGRES_DB: djangodb
SECRET_KEY: "${SECRET_KEY}"
ALLOWED_HOSTS: "recipes.yourdomain.com"
GUNICORN_MEDIA: 0
TZ: America/Los_Angeles
depends_on:
db:
condition: service_healthy
nginx:
image: nginx:alpine
container_name: tandoor_nginx
restart: unless-stopped
ports:
- "8090:80"
volumes:
- tandoor_media:/media
- tandoor_static:/static
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- web
volumes:
db_data:
tandoor_media:
tandoor_static:
# nginx.conf
server {
listen 80;
location /media/ {
alias /media/;
}
location /static/ {
alias /static/;
}
location / {
proxy_pass http://tandoor:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# .env
POSTGRES_PASSWORD=your-db-password
SECRET_KEY=your-random-50-char-secret-key
docker compose up -d
Part 2: HTTPS with Caddy
recipes.yourdomain.com {
reverse_proxy localhost:8090
}
Visit https://recipes.yourdomain.com → create admin account on first visit.
Part 3: Import Recipes
From URL (automatic scraping)
- Recipes → Create → Import from URL
- Paste URL:
https://www.allrecipes.com/recipe/... - Tandoor scrapes the page and extracts:
- Title, description, image
- Ingredients with amounts
- Instructions (step by step)
- Nutrition information (if available)
- Review and save
Supported sites include:
- Allrecipes, Food Network, Epicurious
- NYT Cooking, Serious Eats, Bon Appétit
- BBC Food, BBC Good Food
- Minimalist Baker, Cookie and Kate
- Plus 2,000+ more via recipe schema.org markup
Manual recipe entry
- Recipes → Create → New Recipe
- Add ingredients with amounts and units
- Add steps with rich text editor
- Set cooking time, servings, nutrition
Import from other apps
# Import from Paprika (export to .paprikarecipes):
# Recipes → Import → Paprika
# Import from JSON/CSV:
# Recipes → Import → Bulk Import
Part 4: Meal Planning
Weekly planner
- Meal Plan → + Add
- Select recipe, date, meal type (breakfast/lunch/dinner/snack)
- Set number of servings
Drag and drop
The planner shows a weekly grid — drag recipes between days to plan your week.
Generate shopping list from meal plan
- Meal Plan → Select meals (checkboxes)
- Shopping → Create shopping list from selection
- Tandoor combines ingredients, merges duplicates, scales by servings:
- Recipe A needs 2 cups flour (4 servings)
- Recipe B needs 1 cup flour (2 servings)
- Combined: 3 cups flour
Part 5: Shopping Lists
Smart ingredient combining
Tandoor automatically:
- Converts units (cups → grams using density data)
- Merges the same ingredient from multiple recipes
- Deducts what you already have (if using inventory tracking)
Supermarket sorting
- Settings → Shopping → Supermarkets
- Add a supermarket:
Whole Foods - Add categories: Produce, Dairy, Meat, Bakery, Dry Goods
- Assign ingredients to categories (one-time setup)
- Shopping list automatically sorts items by store layout
Share shopping list
Generate a share link to a read-only list:
- Shopping list → Share → copy link
- Family members open link on phone while shopping
Part 6: Nutrition Tracking
# Tandoor uses Open Food Facts for nutrition data.
# Enable in settings:
# Settings → Integrations → Open Food Facts: Enabled
# Per recipe, nutrition shows:
# - Calories per serving
# - Protein, Fat, Carbohydrates
# - Fiber, Sugar, Sodium
Per-meal nutrition in the meal planner shows daily totals.
Part 7: REST API
# Get API token:
# Settings → API → Create Token
TOKEN="your-api-token"
BASE="https://recipes.yourdomain.com"
# List all recipes:
curl "$BASE/api/recipe/" \
-H "Authorization: Token $TOKEN" | jq '.[].name'
# Get a recipe by ID:
curl "$BASE/api/recipe/42/" \
-H "Authorization: Token $TOKEN"
# Create a recipe:
curl -X POST "$BASE/api/recipe/" \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Pasta Carbonara", "servings": 4}'
# Get shopping list:
curl "$BASE/api/shopping-list-entry/" \
-H "Authorization: Token $TOKEN" | jq '.[].ingredient.name'
# Get today's meal plan:
curl "$BASE/api/meal-plan/?from_date=$(date +%Y-%m-%d)&to_date=$(date +%Y-%m-%d)" \
-H "Authorization: Token $TOKEN"
Home Assistant integration
# configuration.yaml — show tonight's dinner:
sensor:
- platform: rest
name: Tandoor Tonight
resource: https://recipes.yourdomain.com/api/meal-plan/
params:
from_date: "{{ now().strftime('%Y-%m-%d') }}"
to_date: "{{ now().strftime('%Y-%m-%d') }}"
headers:
Authorization: !secret tandoor_token
value_template: >
{{ value_json | selectattr('meal_type', 'eq', 3) |
map(attribute='recipe.name') | join(', ') }}
Part 8: Grocy Integration
Tandoor + Grocy creates a complete food management stack:
Tandoor meal plan → generate shopping list
Grocy shopping list → check off items while shopping
Grocy inventory → know what you have
Tandoor → filter meal plans to what's in stock
# Enable Grocy sync in Tandoor:
# Settings → Integrations → Grocy
# Grocy URL: https://home.yourdomain.com
# Grocy API Key: your-grocy-api-key
Maintenance
# Update:
docker compose pull
docker compose up -d
# Database backup:
docker exec tandoor-db-1 pg_dump -U djangouser djangodb \
| gzip > tandoor-db-$(date +%Y%m%d).sql.gz
# Media backup (recipe images):
tar -czf tandoor-media-$(date +%Y%m%d).tar.gz \
$(docker volume inspect tandoor_tandoor_media --format '{{.Mountpoint}}')
# Logs:
docker compose logs -f web
See also: Grocy — pair with Tandoor for inventory integration
See all open source recipe tools at OSSAlt.com/categories/home.