# Mealie - Recipe Manager & Meal Planner # Domain: ${SUBDOMAIN}.${DOMAIN} # Database: None (SQLite, built-in) # RAM: ~200MB (mem_limit: 1000M) | Pi-compatible: Yes (arm64 only) # # Environment variables: # DOMAIN - Your domain (e.g., demo-felhom.eu) # # Storage layout: # Recipe data/images → mealie_data (named volume, NVMe — moderate size) # # First-time setup: # Default login: changeme@example.com / MyPassword # Change immediately after first login! services: mealie: image: ghcr.io/mealie-recipes/mealie:v3.28.0 container_name: mealie restart: unless-stopped environment: - ALLOW_SIGNUP=false # R-747 (decided by CC unattended 2026-10-01, `09` §3 decision 57 — operator may reverse): mealie locks the ACCOUNT # after 5 wrong logins for SECURITY_USER_LOCKOUT_TIME hours (default 24), and its login names (admin, # changeme@example.com) are public — a stranger could lock the household out for a day. 1 hour (the unit's # minimum) keeps the guard; mealie's hourly job lifts it, so a lock lasts 1-2 h (measured on 9202: 120 min). - SECURITY_USER_LOCKOUT_TIME=1 - PUID=1000 - PGID=1000 - TZ=Europe/Budapest - MAX_WORKERS=1 - WEB_CONCURRENCY=1 - BASE_URL=https://${SUBDOMAIN}.${DOMAIN} # App-email (managed relay). Injected by the controller only when app-email is on # (global + per-app); empty SMTP_HOST = Mealie mail stays disabled. Mealie has no # accept-invalid-cert option, so the relay uses plaintext (NONE) to the on-box shim — # the spike-validated mode. See .felhom.yml smtp_mapping. - SMTP_HOST=${SMTP_HOST:-} - SMTP_PORT=${SMTP_PORT:-25} - SMTP_AUTH_STRATEGY=${SMTP_AUTH_STRATEGY:-NONE} - SMTP_FROM_NAME=${SMTP_FROM_NAME:-} - SMTP_FROM_EMAIL=${SMTP_FROM_EMAIL:-} volumes: - mealie_data:/app/data/ networks: - traefik-public deploy: resources: limits: memory: 1000M healthcheck: test: ["CMD-SHELL", "python3 -c \"import socket; s=socket.create_connection(('127.0.0.1',9000),2); s.close()\""] interval: 30s timeout: 5s retries: 3 start_period: 60s labels: - "traefik.enable=true" - "traefik.http.routers.mealie.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)" # R-753: this app reads the LEFTMOST X-Forwarded-For entry, which a stranger writes once traefik keeps the # tunnel's chain — so its chain is removed here (it then reads traefik's X-Real-Ip or peer; never forgeable). - "traefik.http.middlewares.mealie-xff.headers.customrequestheaders.X-Forwarded-For=" - "traefik.http.routers.mealie.middlewares=mealie-xff" - "traefik.http.routers.mealie.entrypoints=websecure" - "traefik.http.routers.mealie.tls=true" - "traefik.http.routers.mealie.tls.certresolver=letsencrypt" - "traefik.http.services.mealie.loadbalancer.server.port=9000" volumes: mealie_data: networks: traefik-public: external: true