# Pantry - Deployment Guide **Version:** 1.0 **Last Updated:** 2026-02-08 --- ## 🎯 Deployment Options ### 1. Docker Compose (Recommended) - 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 ```bash # 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:** ```yaml 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:** ```bash # Database DB_PASSWORD= # JWT (generate: openssl rand -hex 32) JWT_SECRET= # Realtime REALTIME_SECRET= # Supabase Keys (generate via supabase CLI or manually) SUPABASE_ANON_KEY= SUPABASE_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 ```bash # 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 ```bash # 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):** ```bash # 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:** ```yaml 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:** ```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 ```bash # 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 ```bash # 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:** ```bash 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:** ```bash curl https://pantry.yourdomain.com/health ``` **Supabase:** ```bash curl http://your-server:54321/health ``` ### Logs **Docker Compose:** ```bash # 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:** ```bash docker system df ``` **Database Size:** ```sql SELECT pg_size_pretty(pg_database_size('pantry')) AS db_size; ``` **Container Stats:** ```bash docker stats ``` --- ## 🔄 Updates ### Pull Latest Changes ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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:** ```bash docker-compose logs app ``` **Common issues:** - Environment variables missing - Can't connect to Supabase - Port 3000 already in use ### Database connection failed **Check PostgreSQL:** ```bash docker-compose ps postgres docker-compose logs postgres ``` **Test connection:** ```bash docker-compose exec postgres psql -U postgres -d pantry -c "SELECT 1;" ``` ### SSL not working **Caddy:** ```bash # 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 ```bash # Clean Docker docker system prune -a # Clean old images docker image prune -a # Clean volumes (careful!) docker volume prune ``` --- ## 📚 Resources - [Docker Compose Docs](https://docs.docker.com/compose/) - [Coolify Docs](https://coolify.io/docs) - [Supabase Self-Hosting](https://supabase.com/docs/guides/self-hosting) - [Caddy Docs](https://caddyserver.com/docs/) --- **All documentation complete!** Ready to start development.