Skip to main content

Open-source alternatives guide

Self-Host Tandoor Recipes for Meal Planning 2026

Self-host Tandoor Recipes for recipe management with nutrition data in 2026. MIT license, ~5K stars, Python/Django — barcode scanning, shopping lists, meal.

·OSSAlt Team
Share:

TL;DR

Tandoor Recipes (MIT, ~5K GitHub stars, Python/Django) is a feature-rich self-hosted recipe manager with detailed nutritional information. While Mealie focuses on simplicity, Tandoor goes deeper: built-in nutritional database (OpenFoodFacts integration), barcode scanning for ingredients, detailed shopping list with supermarket aisle mapping, and full-text recipe search. If you care about nutrition tracking alongside recipes, Tandoor is the better choice.

Key Takeaways

  • Tandoor: MIT, ~5K stars, Python/Django — recipes + nutrition + shopping + barcode scan
  • OpenFoodFacts: Integration with the open food database for nutritional info
  • Barcode scanning: Scan ingredient barcodes to add to shopping lists
  • Supermarket mapping: Map ingredients to your store's aisles for efficient shopping
  • Meal planning: Weekly planner with nutritional summary
  • Full-text search: Search inside recipes (not just titles)

Tandoor vs Mealie

FeatureTandoorMealie
LicenseMITAGPL 3.0
GitHub Stars~5K~7K
UI ComplexityHigherSimpler
Nutrition dataYes (OpenFoodFacts)Limited
Barcode scanYesNo
Shopping listYes (advanced)Yes (basic)
Supermarket mappingYesNo
Recipe scrapingYesYes (better)
Meal planningYesYes
OCR importYesYes
APIYesYes

Part 1: Docker Setup

# docker-compose.yml
services:
  db_recipes:
    restart: always
    image: postgres:16-alpine
    volumes:
      - postgresql_data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
      POSTGRES_USER: djangodb
      POSTGRES_DB: djangodb

  web_recipes:
    image: vabene1111/recipes:latest
    container_name: tandoor
    restart: always
    ports:
      - "8080:8080"
    volumes:
      - staticfiles:/opt/recipes/staticfiles
      - mediafiles:/opt/recipes/mediafiles
    depends_on:
      - db_recipes
    environment:
      SECRET_KEY: "${SECRET_KEY}"    # openssl rand -hex 64
      DB_ENGINE: django.db.backends.postgresql
      POSTGRES_HOST: db_recipes
      POSTGRES_PORT: "5432"
      POSTGRES_USER: djangodb
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
      POSTGRES_DB: djangodb
      ALLOWED_HOSTS: "meals.yourdomain.com"
      GUNICORN_MEDIA: "0"
      TIMEZONE: "America/Los_Angeles"
      ACCOUNT_EMAIL_SUBJECT_PREFIX: "[Tandoor]"
      DEBUG: "0"

  nginx_recipes:
    image: nginx:mainline-alpine
    restart: always
    ports:
      - "80:80"
    volumes:
      - staticfiles:/static:ro
      - mediafiles:/media:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
    depends_on:
      - web_recipes

volumes:
  postgresql_data:
  staticfiles:
  mediafiles:
# nginx/conf.d/recipes.conf
server {
    listen 80;
    server_name _;
    
    client_max_body_size 16M;
    
    location /static/ {
        alias /static/;
    }
    
    location /media/ {
        alias /media/;
    }
    
    location / {
        proxy_pass http://web_recipes:8080;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
# Generate secret key:
openssl rand -hex 64

docker compose up -d

Part 2: HTTPS with Caddy

Replace the nginx container with Caddy for automatic HTTPS:

meals.yourdomain.com {
    handle /static/* {
        file_server {
            root /path/to/staticfiles
        }
    }
    handle /media/* {
        file_server {
            root /path/to/mediafiles
        }
    }
    reverse_proxy web_recipes:8080
}

Or simply proxy through Caddy to the nginx container:

meals.yourdomain.com {
    reverse_proxy localhost:80
}

Part 3: Initial Setup

  1. Visit https://meals.yourdomain.com
  2. Create admin account
  3. Admin Panel → Household → Create Your Household
  4. Invite other users to the household

Part 4: Import Recipes

From URL

  1. Recipes → + New → Import From URL
  2. Paste recipe URL
  3. Tandoor extracts ingredients, instructions, images
  4. Review and save

Manual Entry

  1. Recipes → + New → Manual
  2. Add ingredients (search by name → Tandoor suggests from food database)
  3. Add steps with photos
  4. Set servings, time, difficulty

From OCR (Photo)

  1. Recipes → + → Create From Image
  2. Upload photo of printed/handwritten recipe
  3. Tandoor uses OCR to extract text
  4. Review and correct

Part 5: Nutrition Information

When you add ingredients, Tandoor looks up nutrition data:

  1. Type ingredient name → Tandoor suggests matches from OpenFoodFacts
  2. Select the correct product → nutrition data auto-filled
  3. Recipe shows: calories, protein, carbs, fat per serving

Manual Nutrition Lookup

  1. Foods → Search or Create Food
  2. Type name or scan barcode
  3. Enter or import nutrition data per 100g

Part 6: Shopping Lists

Generate shopping lists from recipes:

  1. Add to Shopping List from any recipe (adjust servings)
  2. Multiple recipes → shopping list auto-combines quantities
  3. Shopping → Lists → My List

Supermarket Mapping

Map ingredients to your supermarket's sections:

  1. Supermarkets → Create Supermarket
  2. Add sections: Produce, Dairy, Meat, Canned Goods, Frozen, Bakery
  3. Foods → Edit Food → assign to supermarket section

Shopping list sorts by supermarket section → efficient shopping path through the store.

Barcode Scanning

  1. Shopping → Scan Barcode
  2. Scan product barcode with your phone camera
  3. Tandoor looks up the product → adds to shopping list

Part 7: Meal Planning

  1. Meal Plan → Weekly View
  2. Click any day → Add Recipe
  3. Set servings (for the family)
  4. Nutritional summary shown for the week
  5. Generate Shopping List from the meal plan

Part 8: Recipe Scraping Sites

Tandoor supports 700+ recipe websites including:

  • allrecipes.com
  • seriouseats.com
  • food52.com
  • BBC Good Food
  • NYT Cooking
  • Bon Appétit
  • Epicurious

Part 9: API

# Get API token from Settings → API → Create Token

# List recipes:
curl https://meals.yourdomain.com/api/recipe/ \
  -H "Authorization: Token YOUR_API_TOKEN"

# Search recipes:
curl "https://meals.yourdomain.com/api/recipe/?query=pasta" \
  -H "Authorization: Token YOUR_API_TOKEN"

# Create a shopping list:
curl -X POST https://meals.yourdomain.com/api/shopping-list/ \
  -H "Authorization: Token YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"recipes": [{"recipe_id": 42, "servings": 4}]}'

Maintenance

# Update Tandoor:
docker compose pull
docker compose up -d

# Run migrations:
docker exec tandoor python manage.py migrate

# Backup:
# Database:
docker exec db_recipes pg_dump -U djangodb djangodb | gzip \
  > tandoor-db-$(date +%Y%m%d).sql.gz

# Media files (uploaded photos):
tar -czf tandoor-media-$(date +%Y%m%d).tar.gz \
  $(docker volume inspect tandoor_mediafiles --format '{{.Mountpoint}}')

# Logs:
docker compose logs -f web_recipes

Why Self-Host Tandoor Recipes

There's no dominant SaaS recipe manager that charges a meaningful monthly fee — the market is fragmented between apps like Paprika ($29.99 one-time per platform), Whisk (ad-supported), and Yummly (acquired by Whirlpool). The case for self-hosting Tandoor isn't primarily about cost savings; it's about features and data longevity.

Paprika is the closest commercial equivalent to Tandoor, and it's genuinely a good app. But it doesn't integrate with OpenFoodFacts for nutritional data, has no barcode scanning, and its supermarket mapping feature is more limited than Tandoor's. If nutrition tracking is part of your cooking workflow — and for anyone managing dietary restrictions, macros, or caloric intake, it is — Tandoor is the most complete open source option available. The integration with OpenFoodFacts gives you nutrition data for hundreds of thousands of products that you can attach to ingredients and see totals at the recipe and meal plan level.

Data longevity is the other argument. Recipe apps have a track record of shutting down or pivoting. Pepperplate shut down in 2019. BigOven went freemium and locked off features. When a recipe app disappears, your recipe collection disappears with it. Tandoor stores everything in a PostgreSQL database you control. Export at any time, restore anywhere. If you've spent years clipping recipes, building shopping lists, and tagging meals by dietary profile, that data has real value and deserves durable storage.

The household-sharing model is another differentiator. Tandoor supports multiple users within a shared household. One person builds the weekly meal plan, another generates the shopping list, and the kitchen app shows the recipe to whoever is cooking. No per-seat pricing, no premium tier for family features.

When NOT to self-host Tandoor. The UI has more complexity than simpler alternatives like Mealie. If your goal is "import a recipe URL and be done," Mealie's cleaner interface may suit you better. Tandoor's strength is in the nutrition and shopping features — if you don't need those, you're running extra complexity for nothing. Also, the three-service setup (PostgreSQL, Django app, Nginx) requires a bit more maintenance than a single-container alternative.

Prerequisites

Tandoor runs as three services: a PostgreSQL database, a Django application server (gunicorn), and an Nginx container serving static files and media. Together they consume around 300–400MB of RAM at idle. A Hetzner CX22 (2 vCPU, 4GB RAM) at €4.50/month is more than adequate. If you're consolidating multiple services on one VPS, the CX32 (8GB RAM) at €9.90/month gives you comfortable headroom. See the VPS comparison guide for full provider comparisons.

Docker Engine 24+ and Docker Compose v2 are required. The compose configuration includes three services — plan to review the Nginx configuration in nginx/conf.d/recipes.conf to ensure client_max_body_size is set high enough for recipe image uploads (16MB is the default; increase to 50MB if you import many high-resolution images).

DNS: create an A record for meals.yourdomain.com (or your preferred subdomain) pointing to your VPS IP. If you replace Nginx with Caddy for HTTPS termination, Caddy handles certificate provisioning automatically. Port 80 and 443 must be open.

Generate the SECRET_KEY before first launch with openssl rand -hex 64. This key signs sessions and tokens — losing it after first use invalidates all sessions but doesn't lose data.

Production Security Hardening

Tandoor stores your household's recipe collection, meal history, and optionally nutritional data. It's not high-risk data, but the server itself is a general-purpose Linux host that needs hardening.

UFW firewall. Block all ports except what's needed:

ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

Internal ports (Django's 8080, PostgreSQL's 5432) should never be exposed publicly.

Fail2ban. Protect the login endpoint:

apt install fail2ban -y

Create /etc/fail2ban/jail.local:

[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5

[sshd]
enabled = true
systemctl restart fail2ban

Secrets in .env. The SECRET_KEY and POSTGRES_PASSWORD are stored in your .env file. Keep it readable only by root:

chmod 600 .env

Never commit .env to version control.

SSH hardening. Use key-based authentication:

# /etc/ssh/sshd_config
PasswordAuthentication no
PermitRootLogin no
systemctl restart sshd

Automatic updates. Install unattended-upgrades for automatic OS security patches:

apt install unattended-upgrades -y
dpkg-reconfigure --priority=low unattended-upgrades

Database backups. Your recipe collection lives in PostgreSQL. Back it up daily — the automated backup guide covers Restic-based encrypted backups to S3-compatible storage, which works well for PostgreSQL dump files. Also back up the mediafiles volume if you've uploaded recipe photos. For a complete security reference, see the self-hosting security checklist.

Troubleshooting Common Issues

Django app fails to start with "ALLOWED_HOSTS" error. The ALLOWED_HOSTS environment variable must exactly match your domain (e.g., meals.yourdomain.com). If it doesn't match the Host header Nginx passes to Django, you'll get a 400 Bad Request or a Django SuspiciousOperation error. Check your .env and restart the web_recipes container.

Static files not loading (CSS/JS broken after deployment). Tandoor separates static file serving to Nginx. If the staticfiles volume isn't populated, CSS won't load. Run the collectstatic command manually: docker exec tandoor python manage.py collectstatic --noinput. This is usually a one-time step that happens automatically on first start, but can fail silently if there's a permissions issue on the volume.

Recipe import from URL fails. Tandoor uses recipe-scrapers to extract structured data from recipe websites. If a URL fails, check whether the site is on the supported list (700+ sites). Unsupported sites can sometimes be imported with the browser extension or manually. If a previously working site stops importing, it may have changed its HTML structure — file an issue on the Tandoor GitHub with the failing URL.

PostgreSQL "connection refused" on startup. The web_recipes service uses depends_on: db_recipes, but Docker's health checking doesn't wait for PostgreSQL to be fully initialized — just for the container to start. On slow hosts, the database may not be ready by the time Django tries to connect. Add a restart: always to web_recipes in your compose file so it retries automatically, or add a health check to the db_recipes service.

Migrations fail after Tandoor update. Always run migrations after pulling a new Tandoor image: docker exec tandoor python manage.py migrate. If migrations fail due to a database conflict, check the Tandoor changelog for breaking schema changes. Restore from a pre-update backup if needed.

Media uploads (recipe photos) not persisting. Verify the mediafiles Docker volume is correctly mounted at /opt/recipes/mediafiles in the Django container and at /media in the Nginx container. If they're not pointing to the same volume, uploaded images are accessible immediately but disappear after a container restart.

Nutritional data not appearing for ingredients. When you type an ingredient name, Tandoor queries OpenFoodFacts for matching products. If no results appear, it may be that the specific product isn't in the OpenFoodFacts database (it's community-maintained and more comprehensive for packaged foods than fresh produce), or there may be a connectivity issue reaching the OpenFoodFacts API. You can add nutritional data manually by going to Foods → Create Food and entering per-100g values directly.

Supermarket layout not appearing in shopping list. Supermarket mapping requires: creating a supermarket, adding categories to it, and then assigning each food item to a category. The category assignment is the step most people miss. Go to Foods → search for an ingredient → Edit → assign it to a supermarket category. Once foods are mapped, the shopping list sorts by your store's layout automatically.

Recipe scraping fails on a supported site. Recipe websites frequently update their HTML structure, which breaks scrapers. If a site that should be supported returns an empty import, check the Tandoor GitHub issues to see if others have reported the same site. The recipe-scrapers library that Tandoor uses is community-maintained and usually has a fix within days of a site update. Update Tandoor to the latest version and retry — docker compose pull && docker compose up -d is often all that's needed.

Meal plan shows incorrect calorie totals. Calorie calculations depend on the nutritional data attached to each food item in your library. If a food item was added without nutritional data, its contribution to the meal plan total is zero. Review the foods used in your planned recipes under Foods → check for any showing N/A for calories, and either link them to OpenFoodFacts entries or manually enter per-100g values.

See the best open source alternatives to Notion for additional self-hosted knowledge management tools that pair well with a recipe database.

See all open source food and lifestyle tools at OSSAlt.com/categories/lifestyle.

The SaaS-to-Self-Hosted Migration Guide (Free PDF)

Step-by-step: infrastructure setup, data migration, backups, and security for 15+ common SaaS replacements. Used by 300+ developers.

Join 300+ self-hosters. Unsubscribe in one click.