# Banatie Production Deployment Guide > Last Updated: December 23, 2025 This guide covers deploying Banatie to a VPS with Docker. For local development, see [environment.md](./environment.md). ## Overview Banatie is deployed as an isolated ecosystem with: - **Landing** (Next.js 15.5.9) → banatie.app - **API** (Express.js) → api.banatie.app - **PostgreSQL** (15-alpine) → Database - **MinIO** (SNMD mode) → Object storage ## Prerequisites - VPS with Docker and Docker Compose - Caddy reverse proxy (or similar) with SSL - DNS records configured - GEMINI_API_KEY from Google AI Studio ## Quick Start ```bash # 1. Create directory structure sudo mkdir -p /opt/banatie/{data,logs,scripts} sudo mkdir -p /opt/banatie/data/{postgres,minio,waitlist-logs,api-results,api-uploads} sudo mkdir -p /opt/banatie/data/minio/{drive1,drive2,drive3,drive4} sudo chown -R $USER:$USER /opt/banatie # 2. Clone repository git clone ~/workspace/projects/banatie-service # 3. Copy production configs cp ~/workspace/projects/banatie-service/infrastructure/docker-compose.production.yml /opt/banatie/docker-compose.yml cp ~/workspace/projects/banatie-service/infrastructure/.env.example /opt/banatie/.env cp ~/workspace/projects/banatie-service/infrastructure/secrets.env.example /opt/banatie/secrets.env cp ~/workspace/projects/banatie-service/infrastructure/init-db.sql /opt/banatie/scripts/ # 4. Configure environment nano /opt/banatie/.env # Edit public variables nano /opt/banatie/secrets.env # Generate and add secrets chmod 600 /opt/banatie/secrets.env # 5. Build and start cd /opt/banatie docker compose --env-file .env --env-file secrets.env build docker compose --env-file .env --env-file secrets.env up -d # 6. Initialize database schema cd ~/workspace/projects/banatie-service pnpm install pnpm --filter @banatie/database db:push # 7. Create master API key curl -X POST https://api.banatie.app/api/bootstrap/initial-key # Or use UI: https://banatie.app/admin/master/ ``` ## Deploy Scripts Located in `scripts/` directory: ```bash # Deploy landing page ./scripts/deploy-landing.sh # Normal deploy ./scripts/deploy-landing.sh --no-cache # Fresh build (when deps change) # Deploy API ./scripts/deploy-api.sh # Normal deploy ./scripts/deploy-api.sh --no-cache # Fresh build ``` ## Configuration Files ### Environment Variables (.env) ```bash NODE_ENV=production PORT=3000 POSTGRES_DB=banatie_db POSTGRES_USER=banatie_user DATABASE_URL=postgresql://banatie_user:@banatie-postgres:5432/banatie_db MINIO_ENDPOINT=banatie-minio:9000 MINIO_PUBLIC_URL=https://cdn.banatie.app API_PUBLIC_URL=https://api.banatie.app NEXT_PUBLIC_API_URL=https://api.banatie.app CORS_ORIGIN=https://banatie.app,https://api.banatie.app ``` ### Secrets (secrets.env) ```bash POSTGRES_PASSWORD= MINIO_ROOT_USER=banatie_admin MINIO_ROOT_PASSWORD= MINIO_ACCESS_KEY=banatie_service MINIO_SECRET_KEY= GEMINI_API_KEY= JWT_SECRET= SESSION_SECRET= ``` Generate secrets with: ```bash openssl rand -base64 32 | tr -d '\n\r ' ``` ## DNS Configuration | Type | Name | Value | |------|------|-------| | A | @ | VPS_IP | | CNAME | www | banatie.app | | CNAME | api | banatie.app | | CNAME | storage | banatie.app | | CNAME | cdn | banatie.app | ## Caddy Configuration Add to your Caddyfile: ```caddy www.banatie.app { redir https://banatie.app{uri} permanent } banatie.app { reverse_proxy banatie-landing:3000 } api.banatie.app { reverse_proxy banatie-api:3000 } storage.banatie.app { reverse_proxy banatie-minio:9001 } cdn.banatie.app { reverse_proxy banatie-minio:9000 header Access-Control-Allow-Origin "*" } ``` ## Troubleshooting ### Permission Denied on Volumes Docker User Namespace Remapping offsets UIDs by 165536: ```bash # Fix permissions for Next.js (uid 1001 → 166537) sudo chown -R 166537:166537 /opt/banatie/data/waitlist-logs # Fix permissions for API (uid 1001 → 166537) sudo chown -R 166537:166537 /opt/banatie/data/api-results sudo chown -R 166537:166537 /opt/banatie/data/api-uploads ``` ### Environment Variables Not Applied Use `docker compose up -d` instead of `docker restart`: ```bash docker compose --env-file .env --env-file secrets.env up -d banatie-api ``` ### NEXT_PUBLIC_* Variables Must be set at both build time AND runtime. Ensure `NEXT_PUBLIC_API_URL` is in .env before building. ### pnpm Workspace Symlinks in Docker The Dockerfiles use simplified single-stage install to avoid symlink issues: ```dockerfile COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./ COPY apps/landing ./apps/landing COPY packages/database ./packages/database RUN pnpm install --frozen-lockfile RUN pnpm --filter @banatie/landing build ``` ### Database Connection Refused URL-encode special characters in DATABASE_URL: - `=` → `%3D` - `@` → `%40` - `#` → `%23` ## Known Issues ### Healthcheck Showing "Unhealthy" Alpine images don't have `curl` by default. The healthcheck uses `wget` but may still show unhealthy in some cases. Services work correctly despite this status. ### Next.js Cache Permission Warning ``` EACCES: permission denied, mkdir '/app/apps/landing/.next/cache' ``` This is non-critical - images still work, just not cached. To fix: ```bash sudo chown -R 166537:166537 /opt/banatie/data/landing-cache # And add volume mount for .next/cache ``` ## Full VPS Documentation For complete VPS setup and infrastructure details, see: - VPS Repository: `VPS/docs/banatie-deployment.md` - Deployment Manual: `VPS/manuals/banatie-service-deployment.md`