The lightest self-hosted recipe manager that actually cares about your food memories.
Other recipe apps store recipes. Zest stores the moments around the table.
Quickstart → · User Manual · Issues
Built for homelab users, privacy-focused cooks, and anyone who believes their recipes — and the stories behind them — should live on their own hardware.
Every recipe manager out there treats food like data: title, ingredients, steps, done. But cooking is more than that. It's the Sunday your daughter helped you make pasta for the first time. The improvised dinner that became a family legend. The photo your partner took while you weren't looking.
Zest is a self-hosted culinary diary — recipes + memories + beautiful shareable cards, all running on your own hardware in a single container. Deploy on a Raspberry Pi, Proxmox LXC, NAS, or any Linux VPS with one docker compose up. No cloud, no subscriptions, no telemetry. Just your food story, owned by you.
- Memories — Attach photos, dates, locations, and stories to any recipe. Build a timeline of your cooking life.
- Moment Cards — Generate gorgeous shareable images (Instagram Stories, square, landscape) from your memories with one click.
- Cookbooks — Curate collections and share them via public links or export as beautiful PDFs.
- "What to Cook?" — Tell Zest what ingredients you have, it tells you what you can make.
- 4-Tier Recipe Scraper — Paste any URL. Zest tries recipe-scrapers, JSON-LD, Microdata, then HTML heuristics. It will find the recipe.
- Dead simple to run — One
docker compose up. SQLite. No Postgres, no Redis, no external services.
| Feature | Description | |
|---|---|---|
| 🍳 | Full Recipe CRUD | Create, edit, search, filter, rate, favorite. Categories and color-coded tags. |
| 🌐 | URL Import | Paste a link from any cooking site. 4-tier scraping handles even the worst recipe blogs. |
| 📸 | Memories | Photo diary entries linked to recipes. EXIF support, GPS metadata, multi-photo upload. |
| 🎴 | Moment Cards | Auto-generated social cards (1080×1080, 1080×1920, 1200×630) from your memories. |
| 📚 | Cookbooks | Curated collections with custom covers. Share via link or export as PDF. |
| 🔗 | Sharing | Public links for cookbooks. Recipients don't need an account. |
| 🛒 | Shopping List | Add ingredients from any recipe, scaled to your portions. Combine multiple recipes. |
| ⚖️ | Portion Calculator | Scale any recipe from 1 to 100 servings. Fractions displayed beautifully (½, ¼, ⅓). |
| 👨🍳 | Cooking Mode | Full-screen step-by-step with auto-detected timers, Wake Lock, and ingredient panel. |
| 🔍 | "What to Cook?" | Enter available ingredients → get ranked recipe suggestions with missing item counts. |
| 📄 | PDF Export | Professional cookbook PDFs with cover page, recipe cards, and proper typography. |
| 💾 | Automatic Backups | Scheduled database + image backups. Manual export/import. Full disaster recovery. |
| 🌙 | Dark Mode | Full dark theme. Respects your preference. |
| 📱 | Mobile Friendly | Responsive design for desktop and mobile. |
| 🔒 | Auth + Rate Limiting | JWT auth, bcrypt passwords, rate-limited endpoints. |
| 🍎 | iPhone Photos | HEIC/HEIF support out of the box. |
git clone https://github.com/MartinSantosT/myzest.git
cd myzest
cp .env.example .env
docker compose up -dOpen http://localhost:8000 and that's it. First-time setup takes about 30 seconds.
First launch: when you visit a fresh Zest install, the server detects there are no users yet and redirects you to /welcome — a one-time setup screen. The first user that registers becomes admin and gets 12 example recipes with photos to explore.
python -c "import secrets; print(secrets.token_urlsafe(32))"Paste the result in your .env file:
ZEST_SECRET_KEY=your-generated-secret-heremyzest/
├── app/
│ ├── main.py # FastAPI application (all endpoints)
│ ├── models.py # SQLAlchemy models
│ ├── schemas.py # Pydantic schemas
│ ├── database.py # Database connection
│ ├── scrapers.py # 4-tier recipe scraping engine
│ ├── card_generator.py # Pillow-based moment card generator
│ └── static/
│ ├── index.html # SPA frontend
│ ├── shared.html # Public cookbook viewer
│ ├── uploads/ # User photos (volume-mounted)
│ └── js/ # 26 ES modules
│ ├── app.js # Module orchestrator
│ ├── api.js # API client
│ ├── recipes.js # Recipe management
│ ├── memories.js # Culinary diary
│ ├── cookbooks.js # Collections
│ ├── shoppingList.js
│ ├── calculator.js
│ ├── whatToCook.js
│ └── ... # 18 more modules
├── tests/ # pytest test suite
├── data/ # SQLite database + backups (volume-mounted)
├── screenshots/ # Repo screenshots (excluded from Docker image)
├── Dockerfile
├── docker-compose.yml
├── requirements.txt # Runtime deps (pinned)
├── requirements-dev.txt # Test deps (pytest, httpx)
├── .dockerignore
├── .env.example
├── MANUAL.md # User manual
└── README.md
Stack: FastAPI · SQLAlchemy · SQLite · Pillow · ReportLab · Vanilla JS (ES Modules) · Tailwind CSS
No frontend build step. No webpack. No node_modules. The frontend is a single HTML file with 26 ES modules loaded natively by the browser.
All configuration is done through environment variables in .env:
| Variable | Default | Description |
|---|---|---|
ZEST_SECRET_KEY |
(empty) | JWT signing key. Generate a unique value with python3 -c "import secrets; print(secrets.token_urlsafe(32))" and put it in your .env. If left empty, Zest auto-generates one in data/.secret_key and logs a warning until you set it explicitly. See MANUAL.md → "Secure your instance" for details. |
ALLOW_PUBLIC_REGISTRATION |
true |
Whether new users can register after the first admin is created. Default true is suitable for a family or a homelab on a closed network. Set false if you expose Zest to the public internet — only the admin can register, additional users must be invited (feature coming soon). The very first user (initial setup) is always allowed regardless of this setting. See MANUAL.md → "Public registration". |
ALLOW_PRIVATE_NETWORKS |
false |
Allow the recipe scraper to fetch URLs that resolve to private IP ranges (10.x, 172.16.x, 192.168.x). Disabled by default to prevent SSRF attacks. Enable only if you intentionally want to scrape recipes from another service in your own LAN. Loopback (127.x) and cloud metadata endpoints (169.254.x) remain blocked even when this is enabled. See MANUAL.md for details. |
| Path | Purpose |
|---|---|
./data:/app/data |
SQLite database, automatic backups, the .secret_key fallback file |
./app/static/uploads:/app/app/static/uploads |
User-uploaded photos (recipes, memories, cookbook covers, avatars) |
For a complete file-system backup, snapshot both paths. The in-app backup feature (Settings → Backups) packages them into a single ZIP automatically.
Default port is 8000. Change it in docker-compose.yml:
ports:
- "3000:8000" # Access on port 3000cd myzest
git pull
docker compose down && docker compose up -d --buildYour data is preserved. Zest stores everything in volume-mounted directories on your host:
./data/— SQLite database, automatic backups, the persisted secret key./app/static/uploads/— your uploaded photos
docker compose down only stops the container; it does not touch these directories. The new container starts back up against the same files. Database schema migrations run automatically on startup (PRAGMA journal_mode=WAL is also enabled for better multi-user concurrency).
Belt and suspenders: Zest also creates an automatic backup before any in-app restore (Settings → Backups). For peace of mind on a major upgrade, you can manually export a full backup ZIP first via Settings → Backups → "Export full backup".
Pinning to a specific version (optional): instead of git pull, run git fetch --tags && git checkout v2.1.3 to lock to a known good release. Use git checkout main && git pull to come back to the latest.
How do I know there is a new version? When the running app version differs from the version stored in your browser, Zest shows an orange banner in the bottom-right of /app saying "New version available (vX.Y.Z) — Reload". Clicking Reload refreshes the page so the new client assets are loaded.
Zest includes a built-in backup system accessible from Settings → Backups:
- Automatic backups — Configure frequency (12h / 24h / 7 days) and retention count
- Manual backups — One-click database + images export as
.zip - Import/Export — Move your data between instances or restore from backup
- JSON export — Export recipes as portable JSON for interoperability
Backups are stored in ./data/backups/ on the host.
Tests are not shipped in the production image. Run them locally with the dev requirements:
pip install -r requirements-dev.txt
pytest tests/ -vOr, against the running container (the test suite is mounted from the repo, not from the image):
docker run --rm -v "$PWD":/app -w /app python:3.11-slim sh -c "pip install -r requirements-dev.txt && pytest tests/ -v"Example Nginx config for exposing Zest with HTTPS:
server {
listen 443 ssl http2;
server_name zest.example.com; # e.g. myzest.app
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
client_max_body_size 20M;
location / {
proxy_pass http://127.0.0.1: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;
}
}Works great behind Caddy, Traefik, or Nginx Proxy Manager too.
curl http://localhost:8000/api/health
# {"status": "healthy", "recipes": 42, "users": 1}Perfect for Uptime Kuma, Healthchecks.io, or any monitoring tool.
- Recipe scraping uses a 4-tier fallback:
recipe-scraperslibrary (400+ sites) → JSON-LD structured data → Microdata attributes → CSS heuristic selectors. If a recipe exists on the page, Zest will find it. - Moment Cards are generated server-side with Pillow. No external APIs, no Canvas tricks. Pure Python image generation with gradient overlays, typography, and branding.
- PDF export uses ReportLab with DejaVu fonts (bundled in the Docker image) for proper Unicode support.
- Auth uses bcrypt with automatic legacy hash migration. Rate limiting via slowapi on sensitive endpoints.
- Frontend is a true SPA built with vanilla JavaScript ES modules. Zero dependencies. Zero build tools. Fast.
- Image processing automatically resizes uploads to max 1920px, converts HEIC to JPEG, and preserves EXIF metadata.
Found a bug or have a feature idea? Open an issue or submit a PR — contributions are welcome.
MIT — do whatever you want with it.
🍊 Zest — Because recipes deserve memories.


