# 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 <repo> ~/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:<password>@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=<generated>
MINIO_ROOT_USER=banatie_admin
MINIO_ROOT_PASSWORD=<generated>
MINIO_ACCESS_KEY=banatie_service
MINIO_SECRET_KEY=<generated>
GEMINI_API_KEY=<your-key>
JWT_SECRET=<generated>
SESSION_SECRET=<generated>
```

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`
