Files
pantry/docs/DEPLOYMENT.md
Claw 812c0ace74 docs: Add comprehensive technical documentation
Complete documentation suite:
- DATABASE.md: Full schema, RLS policies, functions, queries
- API.md: Supabase client API, Edge functions, realtime
- DEVELOPMENT.md: Setup, workflow, conventions, testing
- DEPLOYMENT.md: Docker Compose, Coolify, monitoring, backups

Ready for development to begin.
2026-02-08 18:56:46 +00:00

12 KiB

Pantry - Deployment Guide

Version: 1.0
Last Updated: 2026-02-08


🎯 Deployment Options

  • Single-server deployment
  • All services in one stack
  • Easy to self-host

2. Coolify

  • UI-based deployment
  • Automatic SSL via Traefik
  • One-click updates

3. Manual (Advanced)

  • Custom infrastructure
  • Kubernetes, etc.

🐳 Docker Compose Deployment

Prerequisites

  • Docker 24+ & Docker Compose
  • Domain name (for SSL)
  • Minimum 2GB RAM
  • 10GB disk space

Quick Deploy

# Clone repository
git clone https://gitea.jeanlucmakiola.de/pantry-app/pantry.git
cd pantry

# Copy production config
cp .env.example .env.production
nano .env.production  # Edit variables

# Start services
docker-compose -f docker/docker-compose.prod.yml up -d

# Check status
docker-compose ps

# View logs
docker-compose logs -f

Production Configuration

docker/docker-compose.prod.yml:

version: '3.8'

services:
  # PostgreSQL (Supabase)
  postgres:
    image: supabase/postgres:15
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: pantry
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - pantry-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Supabase Auth (GoTrue)
  auth:
    image: supabase/gotrue:v2.151.0
    restart: unless-stopped
    environment:
      GOTRUE_DB_DRIVER: postgres
      GOTRUE_DB_DATABASE_URL: postgres://postgres:${DB_PASSWORD}@postgres:5432/pantry
      GOTRUE_SITE_URL: ${PUBLIC_APP_URL}
      GOTRUE_URI_ALLOW_LIST: ${PUBLIC_APP_URL}
      GOTRUE_JWT_SECRET: ${JWT_SECRET}
      GOTRUE_JWT_EXP: 3600
      # Email/Password
      GOTRUE_EXTERNAL_EMAIL_ENABLED: true
      # OIDC (optional)
      GOTRUE_EXTERNAL_GOOGLE_ENABLED: ${AUTH_GOOGLE_ENABLED:-false}
      GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID}
      GOTRUE_EXTERNAL_GOOGLE_SECRET: ${AUTH_GOOGLE_SECRET}
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - pantry-network

  # Supabase Realtime
  realtime:
    image: supabase/realtime:v2.30.23
    restart: unless-stopped
    environment:
      DB_HOST: postgres
      DB_NAME: pantry
      DB_USER: postgres
      DB_PASSWORD: ${DB_PASSWORD}
      DB_PORT: 5432
      SECRET_KEY_BASE: ${REALTIME_SECRET}
      REPLICATION_MODE: RLS
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - pantry-network

  # Supabase Storage (optional - for product images)
  storage:
    image: supabase/storage-api:v1.0.6
    restart: unless-stopped
    environment:
      POSTGREST_URL: http://rest:3000
      PGRST_JWT_SECRET: ${JWT_SECRET}
      DATABASE_URL: postgres://postgres:${DB_PASSWORD}@postgres:5432/pantry
      STORAGE_BACKEND: file
      FILE_STORAGE_BACKEND_PATH: /var/lib/storage
    volumes:
      - storage_data:/var/lib/storage
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - pantry-network

  # PostgREST (Supabase API)
  rest:
    image: postgrest/postgrest:v12.0.2
    restart: unless-stopped
    environment:
      PGRST_DB_URI: postgres://postgres:${DB_PASSWORD}@postgres:5432/pantry
      PGRST_DB_SCHEMAS: public,storage
      PGRST_DB_ANON_ROLE: anon
      PGRST_JWT_SECRET: ${JWT_SECRET}
      PGRST_DB_USE_LEGACY_GUCS: false
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - pantry-network

  # Pantry App (Nuxt)
  app:
    build:
      context: ../app
      dockerfile: Dockerfile
    restart: unless-stopped
    environment:
      SUPABASE_URL: http://rest:3000
      SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY}
      PUBLIC_APP_URL: ${PUBLIC_APP_URL}
    ports:
      - "3000:3000"
    depends_on:
      - rest
      - auth
      - realtime
    networks:
      - pantry-network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000"]
      interval: 30s
      timeout: 10s
      retries: 3

  # Reverse Proxy (Caddy with auto-SSL)
  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - pantry-network

volumes:
  postgres_data:
  storage_data:
  caddy_data:
  caddy_config:

networks:
  pantry-network:
    driver: bridge

Environment Variables

.env.production:

# Database
DB_PASSWORD=<generate-strong-password>

# JWT (generate: openssl rand -hex 32)
JWT_SECRET=<generate-strong-jwt-secret>

# Realtime
REALTIME_SECRET=<generate-strong-secret>

# Supabase Keys (generate via supabase CLI or manually)
SUPABASE_ANON_KEY=<your-anon-key>
SUPABASE_SERVICE_KEY=<your-service-key>

# App
PUBLIC_APP_URL=https://pantry.yourdomain.com

# OIDC (optional)
AUTH_GOOGLE_ENABLED=false
AUTH_GOOGLE_CLIENT_ID=
AUTH_GOOGLE_SECRET=

# Open Food Facts
OPENFOODFACTS_API_URL=https://world.openfoodfacts.org

Generate Secrets

# DB Password
openssl rand -hex 32

# JWT Secret (needs to be the same for all Supabase services)
openssl rand -hex 32

# Supabase Keys (use supabase CLI)
supabase init
supabase start
# Copy anon key and service key from output

Caddy Configuration

docker/Caddyfile:

{
  email your-email@example.com
}

pantry.yourdomain.com {
  reverse_proxy app:3000
  
  # WebSocket support (for Supabase Realtime)
  @websockets {
    header Connection *Upgrade*
    header Upgrade websocket
  }
  reverse_proxy @websockets realtime:4000
}

Database Migrations

# Apply migrations on first deploy
docker-compose -f docker/docker-compose.prod.yml exec postgres \
  psql -U postgres -d pantry -f /migrations/001_schema.sql

# Or use Supabase CLI
supabase db push --db-url postgres://postgres:${DB_PASSWORD}@localhost:5432/pantry

SSL Certificates

Caddy (automatic):

  • Caddy automatically requests Let's Encrypt certificates
  • Renews certificates automatically

Manual (Certbot):

# Install certbot
sudo apt-get install certbot

# Request certificate
sudo certbot certonly --standalone -d pantry.yourdomain.com

# Add to Nginx/Caddy config

☁️ Coolify Deployment

Prerequisites

  • Coolify instance running
  • Domain name
  • Git repository access

Deploy Steps

1. Add Resource in Coolify:

  • Type: Docker Compose
  • Source: Git Repository
  • Repository: https://gitea.jeanlucmakiola.de/pantry-app/pantry.git
  • Branch: main
  • Compose File: docker/docker-compose.prod.yml

2. Configure Environment:

  • Add environment variables from .env.production
  • Generate secrets via Coolify UI

3. Set Domain:

  • Domain: pantry.yourdomain.com
  • SSL: Enable (Let's Encrypt automatic)

4. Deploy:

  • Click "Deploy"
  • Coolify builds and starts services
  • Traefik automatically handles SSL

5. Verify:

  • Visit https://pantry.yourdomain.com
  • Check logs in Coolify UI

Coolify-Specific Config

docker/docker-compose.coolify.yml:

version: '3.8'

services:
  # ... (same as prod, but with Coolify labels)
  
  app:
    build:
      context: ../app
      dockerfile: Dockerfile
    restart: unless-stopped
    environment:
      SUPABASE_URL: http://rest:3000
      SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY}
    labels:
      - "coolify.managed=true"
      - "traefik.enable=true"
      - "traefik.http.routers.pantry.rule=Host(`${DOMAIN}`)"
      - "traefik.http.services.pantry.loadbalancer.server.port=3000"
    networks:
      - pantry-network
      - coolify  # Coolify proxy network

networks:
  pantry-network:
    driver: bridge
  coolify:
    external: true

📦 App Dockerfile

app/Dockerfile:

FROM oven/bun:1 AS builder

WORKDIR /app

# Install dependencies
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile

# Copy source
COPY . .

# Build app
RUN bun run build

# Production image
FROM oven/bun:1-slim

WORKDIR /app

# Copy built app
COPY --from=builder /app/.output /app/.output

# Expose port
EXPOSE 3000

# Start app
CMD ["bun", "run", ".output/server/index.mjs"]

🔧 Post-Deployment

1. Apply Migrations

# Via Supabase CLI
supabase db push --db-url postgres://postgres:PASSWORD@your-server:5432/pantry

# Or manually via psql
psql -h your-server -U postgres -d pantry -f supabase/migrations/001_schema.sql

2. Seed Default Data

# Apply seed migrations
psql -h your-server -U postgres -d pantry -f supabase/migrations/002_seed_units.sql
psql -h your-server -U postgres -d pantry -f supabase/migrations/003_seed_tags.sql

3. Create First User

Via Supabase Studio:

  • Access at http://your-server:54323 (if exposed)
  • Go to "Authentication" → "Users"
  • Add user manually

Via API:

curl -X POST https://pantry.yourdomain.com/auth/v1/signup \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "admin@example.com", "password": "secure-password"}'

📊 Monitoring

Health Checks

App:

curl https://pantry.yourdomain.com/health

Supabase:

curl http://your-server:54321/health

Logs

Docker Compose:

# All services
docker-compose logs -f

# Specific service
docker-compose logs -f app
docker-compose logs -f postgres

Coolify:

  • View logs in Coolify UI
  • Real-time log streaming

Metrics

Disk Usage:

docker system df

Database Size:

SELECT 
  pg_size_pretty(pg_database_size('pantry')) AS db_size;

Container Stats:

docker stats

🔄 Updates

Pull Latest Changes

# Stop services
docker-compose down

# Pull latest code
git pull origin main

# Rebuild app
docker-compose build app

# Start services
docker-compose up -d

# Apply new migrations
supabase db push

Coolify Auto-Updates

Enable in Coolify UI:

  • Resource Settings → Auto Deploy
  • Trigger: Git push to main
  • Coolify rebuilds and redeploys automatically

🔐 Security Checklist

  • Strong passwords for all services
  • JWT secret rotated and secured
  • SSL certificates valid
  • Firewall rules configured (only 80/443 exposed)
  • Database backups enabled
  • Environment variables not committed to git
  • Supabase service key kept secret (not exposed to frontend)
  • Rate limiting configured (if needed)
  • CORS configured properly

💾 Backup & Restore

Backup Database

# Full backup
docker-compose exec postgres pg_dump -U postgres pantry > backup.sql

# Compressed
docker-compose exec postgres pg_dump -U postgres pantry | gzip > backup.sql.gz

# Scheduled backup (cron)
0 2 * * * docker-compose -f /path/to/docker-compose.prod.yml exec postgres pg_dump -U postgres pantry | gzip > /backups/pantry-$(date +\%Y\%m\%d).sql.gz

Restore Database

# From backup
docker-compose exec -T postgres psql -U postgres pantry < backup.sql

# From compressed
gunzip -c backup.sql.gz | docker-compose exec -T postgres psql -U postgres pantry

Backup Volumes

# Backup postgres data volume
docker run --rm \
  -v pantry_postgres_data:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/postgres-data.tar.gz /data

# Restore
docker run --rm \
  -v pantry_postgres_data:/data \
  -v $(pwd):/backup \
  alpine tar xzf /backup/postgres-data.tar.gz -C /

🚨 Troubleshooting

App won't start

Check logs:

docker-compose logs app

Common issues:

  • Environment variables missing
  • Can't connect to Supabase
  • Port 3000 already in use

Database connection failed

Check PostgreSQL:

docker-compose ps postgres
docker-compose logs postgres

Test connection:

docker-compose exec postgres psql -U postgres -d pantry -c "SELECT 1;"

SSL not working

Caddy:

# Check Caddy logs
docker-compose logs caddy

# Verify DNS points to server
dig pantry.yourdomain.com

Coolify:

  • Check Traefik logs in Coolify
  • Verify domain configuration

Out of disk space

# Clean Docker
docker system prune -a

# Clean old images
docker image prune -a

# Clean volumes (careful!)
docker volume prune

📚 Resources


All documentation complete! Ready to start development.