Mealie Recipe Manager: Deploy a Self-Hosted Meal Planner using Docker

Learn how to self-host Mealie using Docker Compose. Store recipes, plan meals, and auto-import data from the web.

Mealie Recipe Manager: Deploy a Self-Hosted Meal Planner using Docker

Core Architecture and Backend Database Selection

To deploy Mealie for production or active multi-user environments, choosing the correct backend database is critical. Mealie supports two storage backends: SQLite and PostgreSQL.

SQLite vs. PostgreSQL Backend

  • SQLite (Default): Best for single-user instances or low-resource virtual private servers (VPS). It operates serverless with minimal overhead, storing all data in a single file inside the /app/data volume. However, it lacks robust concurrent write handling and is prone to database locks under high request volume.
  • PostgreSQL: Highly recommended for multi-user setups, automation integrations, or scenarios requiring database backups independent of container state. PostgreSQL handles simultaneous transactions efficiently, avoiding file-lock conditions.

We will focus on the PostgreSQL-backed deployment configuration to guarantee data integrity and scale.

Mealie Volume Bindings and Scraper Integration

Mealie relies on volume mounts to persist its database, uploaded media assets (recipe images), and search indices. In addition, its integrated recipe scraper parses standard schema structures (e.g., Schema.org Microdata or JSON-LD) using a python backend component.

Essential Bindings

  1. /app/data: Stores SQLite databases (if selected), search indices, customized themes, backups, and user settings.
  2. /app/data/media: Isolates image uploads and generated assets for easy caching and proxy configurations.

Recipe Scraper Core Details

Mealie's automatic importer uses python libraries to parse incoming URLs, fetch content, and extract structured recipe details (ingredients, instructions, preparation times, and calorie details). It respects robots.txt permissions but performs requests directly from the container, requiring outbound internet access (port 80/443).

Complete Docker Compose Configuration

The following docker-compose.yml provides a production-ready blueprint. It configures a Mealie frontend/backend container coupled with a PostgreSQL database, secured within an isolated Docker network.

version: '3.8'

services:
  mealie:
    image: ghcr.io/mealie-recipes/mealie:v1.12.0
    container_name: mealie
    restart: always
    environment:
      # Database Settings
      - DB_ENGINE=postgres
      - POSTGRES_USER=mealie_user
      - POSTGRES_PASSWORD=mealie_secure_pass_99
      - POSTGRES_DB=mealie_db
      - POSTGRES_SERVER=mealie-db
      - POSTGRES_PORT=5432

      # Application Settings
      - TZ=UTC
      - BASE_URL=https://mealie.yourdomain.com
      - ALLOW_SIGNUP=false

      # Security & Initialization
      - PUID=1000
      - PGID=1000
    volumes:
      - mealie-data:/app/data
    ports:
      - "9000:9000"
    depends_on:
      mealie-db:
        condition: service_healthy
    networks:
      - mealie-network

  mealie-db:
    image: postgres:15-alpine
    container_name: mealie-db
    restart: always
    environment:
      - POSTGRES_USER=mealie_user
      - POSTGRES_PASSWORD=mealie_secure_pass_99
      - POSTGRES_DB=mealie_db
    volumes:
      - mealie-db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U mealie_user -d mealie_db"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - mealie-network

volumes:
  mealie-data:
  mealie-db-data:

networks:
  mealie-network:
    driver: bridge

Step-by-Step Server Setup and Deployment

Deploying the stack requires preparing your directory structure and setting up the environment.

Step 1: Directory Preparation

Create a dedicated project folder and verify the directory permissions:

mkdir -p /opt/mealie && cd /opt/mealie

Step 2: Environment Variables

Create a .env file to store sensitive credentials instead of hardcoding them within docker-compose.yml:

POSTGRES_PASSWORD=your_highly_secure_random_db_password
BASE_URL=https://mealie.yourdomain.com
TZ=America/New_York

Update your docker-compose.yml environment block to reference these variable placeholders:

    environment:
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - BASE_URL=${BASE_URL}
      - TZ=${TZ}

Step 3: Run the Stack

Run the following command to boot the services in detached mode:

docker compose up -d

Verify that both containers are running and healthy:

docker compose ps

Reverse Proxy Configuration with Nginx

To expose Mealie to the internet securely over HTTPS, configure Nginx as a reverse proxy.

Nginx Virtual Host Configuration

Create a configuration file at /etc/nginx/sites-available/mealie:

server {
    listen 80;
    server_name mealie.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name mealie.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/mealie.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mealie.yourdomain.com/privkey.pem;

    # Security Headers
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";
    add_header Referrer-Policy "no-referrer-when-downgrade";
    add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'";

    # Proxy Configuration
    location / {
        proxy_pass http://127.0.0.1:9000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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;

        # Max upload size for recipe images
        client_max_body_size 20M;
    }
}

Enable the configuration and reload Nginx:

ln -s /etc/nginx/sites-available/mealie /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

Recipe Import and Scraper Mechanics

Once Mealie is online, navigate to the web interface to configure the scraper.

Using the Recipe Parser

Mealie relies on the standard JSON-LD schema pattern embedded within websites. To import a recipe: 1. Navigate to Recipes -> Import from URL. 2. Paste the target URL (e.g., a recipe from a cooking blog). 3. Click Import.

Troubleshooting Scrapers

  • Forbidden / Cloudflare blocks: Some recipes sit behind Cloudflare protection or geo-blocking. If a scrape fails, verify container internet access or set up a custom scraper user-agent header if supported.
  • Manual Parsing fallback: When schema parser fails to locate metadata, Mealie allows you to manually copy and paste raw text, using its built-in parser to match ingredients to amounts.

Automated Backups and Maintenance

Protect your database and media files from VPS storage failures by scheduling automated backups.

Creating a Backup Script

Create /opt/mealie/backup.sh:

#!/bin/bash
BACKUP_DIR="/opt/mealie/backups"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
mkdir -p "$BACKUP_DIR"

# Dump database
docker exec mealie-db pg_dump -U mealie_user mealie_db > "$BACKUP_DIR/mealie_db_$TIMESTAMP.sql"

# Archive media data
tar -czf "$BACKUP_DIR/mealie_media_$TIMESTAMP.tar.gz" -C /var/lib/docker/volumes/mealie_mealie-data/_data .

# Delete backups older than 7 days
find "$BACKUP_DIR" -type f -mtime +7 -delete

Make the script executable:

chmod +x /opt/mealie/backup.sh

Scheduling with Cron

Open crontab:

crontab -e

Add the following line to run backups daily at 2:00 AM:

0 2 * * * /opt/mealie/backup.sh