Mealie

Self-hosted recipe manager and meal planner with a Postgres backend.

ProductivityintermediateProduction-ready defaults2 services

The stack

Generated output

docker-compose.yml
services:
  mealie:
    image: ghcr.io/mealie-recipes/mealie:v3.19.2@sha256:f68e959bf66f4f458893ea58facac71690fe6f2ac7a31466b5cecb41b4e99c02
    restart: unless-stopped
    depends_on:
      - mealie-postgres
    environment:
      ALLOW_SIGNUP: "false"
      BASE_URL: https://${MEALIE_HOST}
      TZ: ${TZ}
      DB_ENGINE: postgres
      POSTGRES_SERVER: mealie-postgres
      POSTGRES_PORT: "5432"
      POSTGRES_USER: mealie
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: mealie
    volumes:
      - mealie-data:/app/data
  mealie-postgres:
    image: postgres:16-alpine@sha256:16bc17c64a573ef34162af9298258d1aec548232985b33ed7b1eac33ba35c229
    restart: unless-stopped
    environment:
      POSTGRES_USER: mealie
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: mealie
      POSTGRES_INITDB_ARGS: --data-checksums
    volumes:
      - mealie-postgres-data:/var/lib/postgresql/data
    healthcheck:
      test:
        - CMD-SHELL
        - pg_isready -U mealie -d mealie
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  mealie-data: {}
  mealie-postgres-data: {}

Replace mealie.example.com with your domain. Generate secrets below.

Environment

.env with generated secrets

.env
# Public hostname Mealie is served on. Used to build BASE_URL (https://<host>) for notification links and OAuth callbacks. Must be HTTPS.
# Timezone for meal-plan dates, scheduled tasks, and logs (IANA tz name).
# PostgreSQL password shared by Postgres and Mealie.

Secrets are generated in your browser via crypto.getRandomValues. Nothing is sent anywhere.

Server-rendered .env template
# Public hostname Mealie is served on. Used to build BASE_URL (https://<host>) for notification links and OAuth callbacks. Must be HTTPS.
MEALIE_HOST=mealie.example.com

# Timezone for meal-plan dates, scheduled tasks, and logs (IANA tz name).
TZ=UTC

# PostgreSQL password shared by Postgres and Mealie.
POSTGRES_PASSWORD=

About

What is Mealie?

Mealie is a self-hosted recipe manager, meal planner, and shopping-list app with a clean web UI and a documented REST API behind it. Its headline feature is recipe import: paste a URL and Mealie scrapes the page's structured recipe data into a clean, ad-free, normalized recipe — ingredients, instructions, times, and image — that you own outright. From there you tag and categorize, scale ingredient quantities, plan meals on a calendar, and generate consolidated shopping lists that group items sensibly. It is built for households: multiple users share a group with shared recipes, meal plans, and lists while keeping individual logins. This stack runs Mealie against a dedicated Postgres database rather than the default SQLite. SQLite is fine for one person kicking the tires, but Postgres is the right backend once more than one person is writing recipes and meal plans concurrently, and it makes backups and upgrades more predictable. The compose file wires Mealie to Postgres over the internal Docker network, sets BASE_URL to your public HTTPS hostname so notification links and OAuth callbacks resolve correctly — the same pattern n8n uses for its webhook URL — and keeps the database off the host entirely. Mealie's own data, including uploaded recipe images, assets, and its internal backups, lives in the mealie-data volume at /app/data, while Postgres persists to its own volume. The reverse proxy at mealie.example.com is the only public entry point; nothing binds to the host directly.

Requirements

Before you start

  • 1 GB RAM minimum, 2 GB comfortable with Postgres running alongside Mealie.
  • Docker 24+ and Docker Compose v2.
  • A persistent disk for both the `mealie-data` and `mealie-postgres-data` volumes — recipes live in Postgres, images and assets in the data volume.
  • A domain with HTTPS. `BASE_URL` must match the public URL, or notification links and OAuth callbacks point at the wrong place.

Deploy

How to deploy

  1. Generate `POSTGRES_PASSWORD` with the "Generate secrets" button.
  2. Set `MEALIE_HOST` to your public hostname (for example `mealie.example.com`); the compose builds `BASE_URL=https://${MEALIE_HOST}` from it.
  3. Optionally set `TZ` so meal-plan dates and scheduled tasks use your local clock.
  4. Start the stack: `docker compose up -d`.
  5. Open `https://mealie.example.com` and log in with the default account `changeme@example.com` / `MyPassword`.
  6. Immediately change the admin email and password in user settings, then open `/admin/site-settings` and confirm the configuration summary is green.

Errors

Common errors & fixes

Notification links, share links, or OAuth logins point at `http://localhost:8080` instead of your domain.

You did not set `BASE_URL`. This stack derives it from `MEALIE_HOST` as `https://${MEALIE_HOST}`, so make sure `.env` has the correct `MEALIE_HOST` and restart Mealie after changing it.

Mealie restarts in a loop or logs "connection refused" / "could not connect to server" on first start.

Postgres is still initializing while Mealie tries to connect. Mealie retries, so give it a few seconds. If it never recovers, check that `POSTGRES_SERVER` matches the database service name (`mealie-postgres`) and that the credentials line up on both services.

After changing `POSTGRES_PASSWORD`, Mealie logs "password authentication failed for user mealie".

Postgres stores the password it was first initialized with inside its data volume; changing the env var later does not update it. Restore the original password, or wipe the `mealie-postgres-data` volume and start fresh (you will lose the database).

Importing a recipe from a URL returns an empty or partial recipe.

Mealie scrapes structured (schema.org) recipe data. Sites that do not publish it, or that block scraping, import poorly — paste the recipe manually or use the bulk importer. This is a limit of the source page, not the server.

Limitations

Honest limitations

  • Backups are your responsibility. Mealie can create backups, but they are written into the `mealie-data` volume — getting them off-site means copying that volume and running `pg_dump` of the database on your own schedule. There is no built-in off-site backup.
  • Recipe URL import depends on the source site exposing structured recipe metadata. Messy or hostile sites import poorly and need manual cleanup.
  • Nutrition fields are free-text labels, not a calculated nutrition engine. Mealie is a recipe and planning tool, not a diet tracker.
  • Postgres is intentionally not exposed to the host. Use `docker compose exec mealie-postgres psql -U mealie` if you need direct database access.

FAQ

Frequently asked

Why Postgres instead of the default SQLite?+

SQLite is fine for one person trying Mealie out, but it serializes writes and is harder to back up consistently. Once a household is editing recipes, meal plans, and shopping lists at the same time, Postgres handles the concurrency and gives you predictable `pg_dump` backups. This stack wires Postgres up for you.

Do I need to set a secret key or token for Mealie?+

No. Unlike some apps, Mealie does not require you to provide a JWT or session secret — it manages token signing itself. The only secret this stack generates is the Postgres password; there is no session secret to invent and no env var for one.

Can I import recipes from websites?+

Yes. Paste a recipe URL and Mealie scrapes the structured data into a clean, normalized recipe with ingredients, steps, and image. It works on the many sites that publish schema.org recipe markup; sites that do not will import partially and can be fixed by hand.

Is Mealie multi-user?+

Yes. Users belong to a group and share recipes, meal plans, and shopping lists while keeping individual logins and permissions. The default `changeme@example.com` account is the first admin — change its credentials immediately.

How do I back it up?+

Two things: the `mealie-data` volume (uploaded images, assets, and Mealie's own backup files) and a `pg_dump` of the Postgres database (recipes, plans, users). The volume alone is not enough, because the recipes themselves live in Postgres.

Related

Related stacks