Self-Host with Docker
Deploy Batida on your own infrastructure using Docker Compose. This guide covers everything you need to get a production-ready instance running.
Prerequisites
- A Linux server (recommended: Ubuntu 22.04+) or any machine with Docker installed
- Docker Engine 20.10+ and Docker Compose v2+
- At least 2 GB RAM and 10 GB disk space
- A domain name pointing to your server (optional, but recommended for TLS)
Architecture
Batida has three components you'll run:
| Component | Description | Port |
|---|---|---|
| batida-api | Rust backend (REST API, event processing) | 5678 |
| batida-web | React frontend (SPA) | 8080 |
| PostgreSQL | Primary database | 5432 |
| Redis | Cache and event idempotency | 6379 |
| NATS | Event bus (JetStream enabled) | 4222 |
Step 1 — Create a Docker Compose File
Create a docker-compose.yml in your deployment directory:
version: '3.8'
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: batida
POSTGRES_USER: batida
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U batida"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
nats:
image: nats:2-alpine
restart: unless-stopped
command: "--jetstream --store_dir /data"
volumes:
- nats_data:/data
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8222/healthz"]
interval: 10s
timeout: 5s
retries: 5
api:
image: git.batida.io/batida/batida-api:latest
restart: unless-stopped
environment:
DATABASE_URL: postgresql://batida:${POSTGRES_PASSWORD:-changeme}@postgres:5432/batida
REDIS_URL: redis://redis:6379
NATS_URL: nats://nats:4222
APP_PUBLIC_URL: ${APP_PUBLIC_URL:-http://localhost:8080}
APP_INTERNAL_PORT: "5678"
ENVIRONMENT: ${ENVIRONMENT:-production}
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:8080}
RUST_LOG: ${RUST_LOG:-info}
# Generate each with: openssl rand -base64 32
JWT_SECRET: ${JWT_SECRET:-changeme_jwt}
ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET:-changeme_admin_jwt}
ENCRYPTION_KEY: ${ENCRYPTION_KEY:-changeme_enc}
# Email (optional)
RESEND_API_KEY: ${RESEND_API_KEY:-}
RESEND_FROM_EMAIL: ${RESEND_FROM_EMAIL:-}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
nats:
condition: service_healthy
ports:
- "5678:5678"
web:
image: git.batida.io/batida/batida-web:latest
restart: unless-stopped
ports:
- "8080:8080"
depends_on:
- api
volumes:
postgres_data:
redis_data:
nats_data:
networks:
default:
name: batida-networkWARNING
Change all default passwords before deploying. Generate each security key with openssl rand -base64 32. All three keys (JWT_SECRET, ADMIN_JWT_SECRET, ENCRYPTION_KEY) must be different.
Step 2 — Configure Environment
Create a .env file in the same directory:
# Database
POSTGRES_PASSWORD=your-strong-password-here
# Security keys (generate each with: openssl rand -base64 32)
JWT_SECRET=your-jwt-secret-here
ADMIN_JWT_SECRET=your-admin-jwt-secret-here
ENCRYPTION_KEY=your-encryption-key-here
# Application
APP_PUBLIC_URL=https://batida.example.com
ENVIRONMENT=production
CORS_ORIGINS=https://batida.example.com
# Email (optional)
RESEND_API_KEY=re_xxxxxxxx
RESEND_FROM_EMAIL=noreply@example.comStep 3 — Pull and Start
docker-compose pull
docker-compose up -dThe API runs database migrations automatically on startup. Wait about 30 seconds for all services to become healthy:
docker-compose psAll services should show healthy or running.
Step 4 — Access Batida
Open your browser at:
http://your-server-ip:8080Create your admin account and organization on first access.
TLS with a Reverse Proxy
For production, put Batida behind a reverse proxy with TLS. Here's a minimal Caddy example:
batida.example.com {
reverse_proxy web:8080
reverse_proxy /api/* api:5678
}Or with Nginx:
server {
listen 443 ssl http2;
server_name batida.example.com;
ssl_certificate /etc/ssl/certs/batida.pem;
ssl_certificate_key /etc/ssl/private/batida.key;
location / {
proxy_pass http://web:8080;
proxy_set_header Host $host;
}
location /api/ {
proxy_pass http://api:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Updating
docker-compose pull
docker-compose up -dThe API handles migrations on startup — no manual migration step needed.
Backups
PostgreSQL data is in a Docker volume. To back it up:
docker-compose exec postgres pg_dump -U batida batida > backup_$(date +%Y%m%d).sqlTo restore:
cat backup_20260523.sql | docker-compose exec -T postgres psql -U batida batidaTroubleshooting
API won't start
Check logs with docker-compose logs api. The most common issue is the database not being ready — the API retries with backoff, so wait 60 seconds before investigating.
Database connection refused
Ensure PostgreSQL is healthy: docker-compose exec postgres pg_isready -U batida.
Events not processing
Check NATS health: docker-compose exec nats wget -qO- http://localhost:8222/healthz.
Port conflicts
If ports 5432, 6379, 4222, 5678, or 8080 are already in use, change the host-side port mappings in your docker-compose.yml (e.g., "5433:5432").
For more help, see Troubleshooting.