Self-Host Your Recipe Manager: Mealie and Tandoor Setup Guide
Recipe websites are among the most ad-saturated, slow-loading, and privacy-invasive corners of the internet. A five-ingredient recipe is buried under autoplay videos, popup newsletters, and seventeen paragraphs about a trip to Tuscany. Mealie and Tandoor are free, open-source recipe managers that run entirely on your own server. Import recipes from any URL with one click, plan your meals for the week, generate shopping lists automatically, and access everything from any device — without ads, without tracking, and without a subscription. This guide covers both apps in detail: what each one does, how they compare, and how to set them up with Docker and access them from anywhere with a Localtonet tunnel.
Why Self-Host a Recipe Manager?
Commercial recipe apps like Paprika, AnyList, and Plan to Eat charge monthly fees to store data you could keep yourself. Free recipe websites survive on advertising that makes them increasingly painful to use. Self-hosting solves both problems: your recipes live on your own hardware, your family accesses them from any browser or phone with no account required, and no third party can shut down the service, raise the price, or sell your cooking habits to advertisers.
Mealie vs Tandoor: Which One to Choose?
Both apps cover the same core use case — recipe storage, meal planning, and shopping lists — but they have different strengths and different levels of complexity. Here is the honest comparison:
| Feature | Mealie v3.17.0 | Tandoor v2.6.9 |
|---|---|---|
| Setup complexity | Single container — simple | Three containers (app + Nginx + PostgreSQL) — moderate |
| Database | SQLite (default, 1-20 users) or PostgreSQL | PostgreSQL only |
| UI design | Modern, clean Vue.js interface. Fast and responsive. | Feature-rich but denser interface. Powerful once learned. |
| Recipe import | URL scraping, manual editor, AI video import (YouTube/TikTok/Instagram via OpenAI) | URL scraping from 500+ sites, manual editor, cookbook import |
| Nutrition tracking | Basic, manual entry | OpenFoodFacts integration, automatic nutritional data, barcode scanning |
| Shopping list | Automatic from meal plan, ingredient merging, unit conversion | Supermarket aisle mapping, section-based organization, offline support |
| Search | Full-text search; fuzzy search with PostgreSQL only | Full-text with trigram similarity (PostgreSQL), ingredient search |
| Multi-user model | Groups (isolated tenants) and Households (shared recipes, separate meal plans) | Multi-user with permission roles, shared and private recipe spaces |
| External integrations | Home Assistant, OIDC/LDAP, REST API, webhooks | Nextcloud, Dropbox, LDAP, REST API |
| Mobile PWA | Yes, installable from browser | Yes, installable from browser |
| Barcode scanning | No | Yes (ingredient lookup via barcode) |
| AI features | Video import from YouTube/TikTok, OpenAI-powered ingredient parsing | AI nutrition estimation (optional) |
| Raspberry Pi support | 64-bit ARM only (no 32-bit) | 64-bit ARM recommended |
| RAM usage | 512 MB recommended | 300-400 MB idle (3 containers combined) |
| License | AGPL | MIT |
| GitHub stars | ~8K+ | ~8.3K |
Simple decision guide
Choose Mealie if: you want the easiest setup, a clean modern interface, AI-powered recipe import from social media videos, Home Assistant integration, or you need to get something running quickly for the family. Mealie's single-container SQLite setup takes five minutes.
Choose Tandoor if: nutrition tracking matters to you, you want to scan barcodes in the grocery store to add ingredients, you need supermarket aisle mapping for efficient shopping, or you are already familiar with Django-based apps. Tandoor is more powerful for serious meal planning but takes more configuration to set up.
Setting Up Mealie with Docker Compose
Mealie runs as a single Docker container. The default SQLite setup is the right choice for most home use. Switch to PostgreSQL only if you need fuzzy search or expect more than 20 concurrent users.
Option A: SQLite (recommended for most users)
services:
mealie:
image: ghcr.io/mealie-recipes/mealie:latest
container_name: mealie
restart: unless-stopped
ports:
- "9925:9000" # access at http://YOUR-IP:9925
deploy:
resources:
limits:
memory: 1000M
volumes:
- ./mealie-data:/app/data
environment:
- ALLOW_SIGNUP=false # set true if you want anyone to register
- PUID=1000
- PGID=1000
- TZ=Europe/Istanbul
- MAX_WORKERS=1 # keep at 1 for low-powered hardware
- WEB_CONCURRENCY=1
- BASE_URL=http://YOUR-SERVER-IP:9925 # update after setting up Localtonet
# Initial admin account
- DEFAULT_EMAIL=admin@yourdomain.com
- DEFAULT_PASSWORD=YourStrongPassword
- DEFAULT_GROUP=Home
- DEFAULT_HOUSEHOLD=Family
Option B: PostgreSQL (fuzzy search + more than 20 users)
services:
mealie:
image: ghcr.io/mealie-recipes/mealie:latest
container_name: mealie
restart: unless-stopped
ports:
- "9925:9000"
deploy:
resources:
limits:
memory: 1000M
volumes:
- ./mealie-data:/app/data
environment:
- ALLOW_SIGNUP=false
- PUID=1000
- PGID=1000
- TZ=Europe/Istanbul
- MAX_WORKERS=1
- WEB_CONCURRENCY=1
- BASE_URL=http://YOUR-SERVER-IP:9925
- DEFAULT_EMAIL=admin@yourdomain.com
- DEFAULT_PASSWORD=YourStrongPassword
- DEFAULT_GROUP=Home
- DEFAULT_HOUSEHOLD=Family
# PostgreSQL connection
- DB_ENGINE=postgres
- POSTGRES_USER=mealie
- POSTGRES_PASSWORD=MealieDBPassword
- POSTGRES_SERVER=mealie-db
- POSTGRES_PORT=5432
- POSTGRES_DB=mealie
depends_on:
- mealie-db
mealie-db:
image: postgres:16-alpine
container_name: mealie-db
restart: unless-stopped
volumes:
- ./mealie-pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_USER=mealie
- POSTGRES_PASSWORD=MealieDBPassword
- POSTGRES_DB=mealie
Start the container
mkdir ~/mealie && cd ~/mealie
# paste docker-compose.yml content, then:
docker compose up -d
docker logs -f mealie
Starting Mealie...
Application startup complete.
Uvicorn running on http://0.0.0.0:9000
Open the web UI and log in
Navigate to http://YOUR-SERVER-IP:9925. Log in with the email and password you set in DEFAULT_EMAIL and DEFAULT_PASSWORD.
Import your first recipe
Click the + button in the top right, choose "Import from URL", paste any recipe URL, and click Import. Mealie scrapes the page and fills in everything automatically. Review the result and save.
Mealie Features Worth Knowing
Groups and Households
Mealie's user model has two levels. Groups are completely isolated tenants — think of running Mealie for two different families with no shared data. Households are subdivisions within a group: household members share recipes and recipe organization, but each household has its own meal plans and shopping lists. For a typical family, one group and one household is all you need. For a shared server among friends, each family can be its own household under one group.
AI Video Import (YouTube, TikTok, Instagram)
Added in v3.13.0, this is Mealie's most distinctive recent feature. Paste a YouTube, TikTok, or Instagram video URL into the import field. Mealie downloads the video, transcribes it using OpenAI Whisper, and converts the spoken recipe into structured data with ingredients and steps. To enable this, add your OpenAI API key in Settings → Integrations → OpenAI.
The AI video import feature requires an OpenAI API key and incurs usage costs based on video length (Whisper transcription). All other Mealie features, including URL scraping from regular recipe websites, work without any API key. The AI features are an optional enhancement, not a requirement.
Unit Standardization and Conversion
Since v3.13.0, Mealie standardizes ingredient units across recipes. When you add a recipe calling for "1 pint" and another calling for "2 cups" of the same ingredient to a shopping list, Mealie merges them correctly into a single measurement. You can also convert between unit systems (metric/imperial) for any recipe.
Image processing (v3.7.0)
Starting with v3.7.0, Mealie uses an improved image processing algorithm that produces thumbnails up to 40-50% smaller on disk while providing higher resolution. After upgrading, run the reprocessing script to apply the improvements to existing recipes:
docker exec -it mealie bash python \
/opt/mealie/lib64/python3.12/site-packages/mealie/scripts/reprocess_images.py \
--workers 2
Setting Up Tandoor with Docker Compose
Tandoor runs as three containers: a PostgreSQL database, a Django application server (gunicorn), and an Nginx container that serves static files and acts as the web frontend. This is more moving parts than Mealie but gives Tandoor better performance for static assets and is the officially supported architecture.
Generate a SECRET_KEY
Tandoor requires a SECRET_KEY environment variable. Generate a random one:
openssl rand -base64 48
K9mP2qR7vX3nL8wT6sY1cA4jE0uH5bF/dN+iM=... (copy this output)
Create docker-compose.yml and .env
SECRET_KEY=your-generated-key-here
DB_ENGINE=django.db.backends.postgresql
POSTGRES_HOST=tandoor-db
POSTGRES_PORT=5432
POSTGRES_USER=tandoor
POSTGRES_PASSWORD=TandoorDBPassword
POSTGRES_DB=tandoor
TANDOOR_PORT=8080
ENABLE_SIGNUP=0 # set 1 to allow public registration
services:
tandoor-db:
image: postgres:16-alpine
container_name: tandoor-db
restart: unless-stopped
volumes:
- ./tandoor-pgdata:/var/lib/postgresql/data
env_file:
- .env
tandoor-app:
image: vabene1111/recipes:latest
container_name: tandoor-app
restart: unless-stopped
volumes:
- ./tandoor-staticfiles:/opt/recipes/staticfiles
- ./tandoor-mediafiles:/opt/recipes/mediafiles
env_file:
- .env
depends_on:
- tandoor-db
tandoor-nginx:
image: nginx:alpine
container_name: tandoor-nginx
restart: unless-stopped
ports:
- "8080:80" # access at http://YOUR-IP:8080
volumes:
- ./tandoor-staticfiles:/static:ro
- ./tandoor-mediafiles:/media:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- tandoor-app
Create the Nginx config file
server {
listen 80;
server_name _;
client_max_body_size 100M;
location /static/ {
alias /static/;
}
location /media/ {
alias /media/;
}
location / {
proxy_pass http://tandoor-app:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Start everything and create the admin account
mkdir ~/tandoor && cd ~/tandoor
# create .env, docker-compose.yml, nginx.conf with content above
docker compose up -d
# Wait 2-3 minutes for database migrations to complete
docker logs -f tandoor-app
# Create admin user
docker exec -it tandoor-app python manage.py createsuperuser
Username: admin
Email address: admin@yourdomain.com
Password: ********
Superuser created successfully.
Open http://YOUR-SERVER-IP:8080 and log in with the superuser credentials.
Tandoor Features Worth Knowing
Nutrition Tracking with OpenFoodFacts
Tandoor integrates with the OpenFoodFacts open database to pull nutritional information for ingredients automatically. When you add an ingredient to a recipe, Tandoor looks it up and fills in calories, protein, carbohydrates, fat, and fiber. You can also enter values manually for ingredients not in the database. The weekly meal plan view shows a nutritional summary for the entire week, which is useful for dietary tracking without a separate app.
Barcode Scanning
The Tandoor mobile PWA can open your phone's camera to scan barcodes. Scan a product's barcode in the grocery store and Tandoor looks it up in OpenFoodFacts, returning the ingredient name and nutritional data. You can add it directly to a shopping list or to a recipe you are editing. This is the feature that most distinguishes Tandoor from Mealie for users who do detailed meal planning with nutritional goals.
Supermarket Layout Mapping
Tandoor lets you map ingredient categories to physical sections of your supermarket. Configure that produce is in aisle 1, dairy in aisle 5, and proteins in the back. When you generate a shopping list from a meal plan, Tandoor groups items by store section so you move through the store in one pass without backtracking. This is configurable per-supermarket, so you can have different layouts for different stores you visit.
Recipe Website Coverage
Tandoor's URL importer supports over 500 recipe websites. For sites not on the supported list, Tandoor falls back to structured data scraping (schema.org Recipe markup), which works on most modern recipe sites. If a site still fails, Tandoor provides a "bookmarklet" tool that lets you scrape the page HTML manually from your browser and paste it into the import dialog.
Accessing Your Recipe Manager from Anywhere
Both Mealie and Tandoor run on a port on your local network. Without any additional configuration, they are only accessible when you are connected to your home Wi-Fi. To access your recipe collection from your phone at the grocery store, or to share it with family members at other locations, you need a way to reach it from the internet.
A Localtonet HTTP tunnel creates a stable public HTTPS URL that forwards to your local port. This works even behind CGNAT, with a dynamic IP, and without any router configuration. The public URL stays the same permanently — set it once in BASE_URL (Mealie) or your Nginx proxy config (Tandoor) and never change it again.
Install Localtonet on the server
chmod +x localtonet
sudo mv localtonet /usr/local/bin/
sudo localtonet authtoken YOUR_TOKEN
Create an HTTP tunnel
In the Localtonet dashboard: Protocol = HTTP, Local IP = 127.0.0.1.
For Mealie: Local Port = 9925, Subdomain = e.g. recipes → recipes.localto.net
For Tandoor: Local Port = 8080, Subdomain = e.g. tandoor → tandoor.localto.net
Update BASE_URL in Mealie
Edit your docker-compose.yml and update the BASE_URL environment variable to your Localtonet URL, then restart:
- BASE_URL=https://recipes.localto.net
docker compose up -d
Install Localtonet as a system service
sudo localtonet --install-service --authtoken YOUR_TOKEN
sudo localtonet --start-service --authtoken YOUR_TOKEN
Localtonet now starts automatically on every boot. Your recipe manager is accessible at your Localtonet URL permanently.
Once you have a public HTTPS URL via Localtonet, open it on your phone in Chrome (Android) or Safari (iOS) and use the browser's "Add to Home Screen" option. This installs Mealie or Tandoor as a PWA, giving it a home screen icon, a full-screen view without the browser bar, and faster load times for pages you have already visited. At the grocery store, your shopping list is one tap away.
Backup and Restore
Mealie backup
Mealie has a built-in backup system at Administration → Backups. Each backup is a zip archive containing the SQLite database (or a PostgreSQL dump if using Postgres) plus all recipe images. Download the archive and store it somewhere safe. To restore, use the Restore option in the same menu.
For a simpler approach, just back up the Docker volume directory:
# Stop container, backup volume, restart
docker compose stop mealie
tar -czf mealie-backup-$(date +%Y%m%d).tar.gz ./mealie-data
docker compose start mealie
Tandoor backup
Tandoor stores data across two locations: the PostgreSQL database and the media files volume. Back up both:
# Dump the PostgreSQL database
docker exec tandoor-db pg_dump -U tandoor tandoor > tandoor-db-$(date +%Y%m%d).sql
# Backup media files (recipe images, etc.)
tar -czf tandoor-media-$(date +%Y%m%d).tar.gz ./tandoor-mediafiles
Troubleshooting Common Problems
| Problem | App | Fix |
|---|---|---|
| Cannot log in after first start | Mealie | Check that DEFAULT_EMAIL and DEFAULT_PASSWORD were set before the first container start. If not, the default credentials are used. Delete the data volume and recreate the container with your credentials set. |
| Recipe URL import fails or returns empty | Both | The target website may block scrapers. Try right-clicking the page in your browser, selecting "View Page Source", copying all the HTML, and using the "Import from HTML" option in the app instead of the URL importer. |
| Images not showing after import | Mealie | Check that BASE_URL matches the URL you are using to access Mealie. A mismatch causes image URLs to resolve incorrectly. Update BASE_URL in docker-compose.yml and restart. |
| Tandoor containers start but app is unreachable | Tandoor | Migrations can take 2-3 minutes on first start. Watch logs with docker logs -f tandoor-app and wait for "Server is ready" before trying to connect. If Nginx returns 502, the Django app is not ready yet. |
| CSRF errors when behind a reverse proxy | Tandoor | Add CSRF_TRUSTED_ORIGINS=https://yourdomain.com to your .env file. Tandoor's Django backend requires this for any proxy or public URL that differs from localhost. |
| Shopping list items not merging correctly | Mealie | Unit merging requires units to be standardized. Go to Settings → Data Management → Units and run the standardization tool. After standardization, units like "cup" and "cups" or "g" and "gram" merge correctly on shopping lists. |
| Container runs but shows "32-bit ARM not supported" | Mealie | Mealie does not support 32-bit ARM. If running on Raspberry Pi, switch to a 64-bit OS (Raspberry Pi OS 64-bit). Raspberry Pi 3, 4, and 5 all support 64-bit. |
| AI video import not working | Mealie | Requires an OpenAI API key configured in Settings → Integrations → OpenAI. Verify the key is valid and has sufficient credit. The Whisper transcription model is used; check OpenAI usage dashboard for errors. |
Frequently Asked Questions
Can I migrate from Mealie to Tandoor (or vice versa)?
Direct migration between the two apps is not supported with a one-click tool because they use different data models. The most reliable approach is to export recipes from one app and re-import them into the other using the URL importer (if the recipes came from public websites) or by manually exporting the recipe data and reformatting it. Both apps support JSON export of recipe data. Tandoor can import from several other recipe apps directly via the "Import" section in Settings.
Can I run both Mealie and Tandoor on the same server?
Yes, they use different ports (9925 for Mealie, 8080 for Tandoor) and have separate Docker networks and volumes. They can coexist on the same machine without conflict. Create two separate HTTP tunnels in Localtonet, one for each app, and give each a different subdomain.
Does Mealie work offline?
Mealie is a PWA with partial offline support. Recipes you have already opened are cached in the browser and available without a network connection. The shopping list also works offline and syncs when connectivity returns. New recipe imports and meal plan changes require an active connection to your Mealie server.
How do I add more users to Mealie?
If ALLOW_SIGNUP=false (recommended), new users must be invited by an admin. Go to Administration → Users → Invite User. This generates an invite link you can send via email or messaging app. The invited user clicks the link, sets their password, and is automatically added to your Group and Household. Alternatively, set ALLOW_SIGNUP=true to let anyone with the URL register themselves.
Can Mealie connect to Home Assistant?
Yes. Mealie has a Home Assistant integration available through HACS (Home Assistant Community Store). Once installed, it creates sensors for today's meal plan, this week's meals, and the current shopping list. You can display today's dinner on a dashboard, trigger automations when meal planning is updated, or use a voice assistant to ask what is for dinner. The integration uses Mealie's REST API and requires your Mealie instance to be reachable from Home Assistant — a Localtonet tunnel works for this if Home Assistant and Mealie are on different machines.
Access Your Recipes from the Grocery Store, Not Just at Home
A Localtonet HTTP tunnel gives Mealie or Tandoor a stable public HTTPS URL you can open on any phone, anywhere. Set it once, install the PWA, and your entire recipe collection and shopping list is always one tap away.
Create Your Free Tunnel →