5.5 KiB
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.
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
# 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:
# 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)
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)
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:
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:
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:
# 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:
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:
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:
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