-
Notifications
You must be signed in to change notification settings - Fork 1
Deployment Guide
This guide covers deploying White-Ops in production, from single-machine setups to multi-PC worker fleets.
- Single Machine Setup
- Multi-PC Fleet
- Docker Compose Configuration
- Environment Variables
- Production Checklist
- TLS / HTTPS
- Backup and Restore
- Monitoring Setup
- Troubleshooting
The simplest deployment runs all services on one machine.
| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 4 cores | 8+ cores |
| RAM | 8 GB | 16+ GB |
| Disk | 20 GB | 100+ GB SSD |
| OS | Ubuntu 22.04 / Debian 12 | Ubuntu 24.04 |
# 1. Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out and back in
# 2. Clone the repository
git clone https://github.com/your-org/white-ops.git
cd white-ops
# 3. Configure environment
cp .env.example .env
# Edit .env with production values (see Environment Variables below)
# 4. Build and start
make build
make up
# 5. Run database migrations
make migrate
# 6. Verify
make status
curl http://localhost:8000/healthThe admin panel is available at http://your-server-ip:3000.
For scaling, run additional workers on separate machines. The master (server, database, web) runs on one machine while workers run on any number of others.
Deploy the full stack as described in Single Machine Setup.
Ensure these ports are accessible from worker machines:
- 8000 -- API server (workers connect here)
- 6379 -- Redis (workers need access)
- 9000 -- MinIO (workers store/retrieve files)
- 8025 -- Mail server (workers send/receive mail)
Use the provided setup script:
# On each worker machine:
./scripts/add-worker.sh <master-ip>The script:
- Installs Docker if not present
- Tests connectivity to the master
- Creates a
docker-compose.ymlfor the worker - Outputs next steps
After running the script:
cd ~/white-ops-worker
# Create .env with credentials matching the master
cat > .env << EOF
REDIS_PASSWORD=same-as-master
MINIO_ROOT_USER=whiteops
MINIO_ROOT_PASSWORD=same-as-master
ANTHROPIC_API_KEY=sk-ant-...
DEFAULT_LLM_PROVIDER=anthropic
DEFAULT_LLM_MODEL=claude-sonnet-4-20250514
EOF
# Start the worker
docker compose up -dThen approve the worker in the admin panel at System > Workers.
See Worker-Management for detailed fleet management.
The main compose file defines all services:
| Service | Image | Ports | Resources |
|---|---|---|---|
postgres |
postgres:16-alpine |
5432 | -- |
redis |
redis:7-alpine |
6379 | -- |
minio |
minio/minio:latest |
9000, 9001 | -- |
server |
Built from ./server
|
8000 | 2 CPU, 1 GB RAM |
worker |
Built from ./worker
|
-- | -- |
web |
Built from ./web
|
3000 | -- |
mail |
Built from ./mail
|
8025 | -- |
Optional (monitoring profile):
| Service | Image | Ports |
|---|---|---|
prometheus |
prom/prometheus:latest |
9090 |
grafana |
grafana/grafana:latest |
3001 |
Overrides for development with hot reload, debug logging, and volume mounts.
make devTo run multiple workers on the same machine:
docker compose up -d --scale worker=3Each worker registers independently with a unique name.
See Configuration for the complete reference table. The critical production variables are:
| Variable | Description | Required |
|---|---|---|
SECRET_KEY |
Application secret (32+ chars) | Yes |
JWT_SECRET_KEY |
JWT signing secret (32+ chars) | Yes |
POSTGRES_PASSWORD |
Database password | Yes |
REDIS_PASSWORD |
Redis password | Yes |
MINIO_ROOT_PASSWORD |
MinIO password (8+ chars) | Yes |
ADMIN_PASSWORD |
Initial admin login password | Yes |
ANTHROPIC_API_KEY |
Anthropic API key (if using Claude) | At least one LLM key |
OPENAI_API_KEY |
OpenAI API key (if using GPT) | At least one LLM key |
DEFAULT_LLM_PROVIDER |
anthropic, openai, google, ollama
|
Yes |
DEFAULT_LLM_MODEL |
Model identifier | Yes |
# Generate a random 64-character string
openssl rand -hex 32
# Or use Python
python3 -c "import secrets; print(secrets.token_urlsafe(48))"Run the pre-deployment check script:
make checkManual checklist:
- Secrets: All passwords and keys are unique, random strings (not defaults)
- DEBUG: Set
DEBUG=false - APP_ENV: Set
APP_ENV=production - CORS:
CORS_ORIGINSset to your actual frontend domain - TLS: HTTPS configured via reverse proxy (see below)
- Firewall: Only expose necessary ports (3000, 8000 or just 443 via proxy)
- Backups: Database backup schedule configured
- Monitoring: Prometheus + Grafana enabled (
make monitoring) - Log Rotation: Docker log rotation configured
- Resource Limits: Docker resource constraints set for each service
- LLM Keys: At least one LLM provider API key configured
- Mail: SMTP configured if external email is needed
- DNS: Domain name pointing to the server
- Workers: All workers approved in admin panel
Add to /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
}
}Then restart Docker: sudo systemctl restart docker
White-Ops does not handle TLS directly. Use a reverse proxy.
server {
listen 443 ssl http2;
server_name whiteops.example.com;
ssl_certificate /etc/letsencrypt/live/whiteops.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/whiteops.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Admin panel
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
# API server
location /api/ {
proxy_pass http://127.0.0.1:8000;
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;
}
# WebSocket
location /ws {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400;
}
}
server {
listen 80;
server_name whiteops.example.com;
return 301 https://$host$request_uri;
}sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d whiteops.example.comUpdate CORS_ORIGINS in .env to match your HTTPS domain.
# Manual backup
make backup
# Saves to ./backups/whiteops_YYYYMMDD_HHMMSS.sql
# Restore from backup
make restore FILE=backups/whiteops_20250101_120000.sqlAdd a cron job on the host:
# Daily backup at 2am, keep 30 days
0 2 * * * cd /opt/white-ops && make backup && find backups/ -mtime +30 -deleteMinIO data is stored in a Docker volume. Back it up with:
# Export volume
docker run --rm -v white-ops_minio_data:/data -v $(pwd)/backups:/backup \
alpine tar czf /backup/minio_$(date +%Y%m%d).tar.gz -C /data .
# Restore volume
docker run --rm -v white-ops_minio_data:/data -v $(pwd)/backups:/backup \
alpine tar xzf /backup/minio_20250101.tar.gz -C /data# Stop services, backup everything
make down
tar czf white-ops-full-backup-$(date +%Y%m%d).tar.gz \
backups/ .env docker-compose.yml
make upmake monitoringThis starts:
-
Prometheus at
http://localhost:9090-- metrics collection -
Grafana at
http://localhost:3001-- dashboards
Default credentials:
- Username: admin
-
Password: value of
GRAFANA_ADMIN_PASSWORDin.env
Pre-configured dashboards are provisioned automatically from monitoring/grafana/dashboards/.
The Prometheus config is at monitoring/prometheus/prometheus.yml. It scrapes:
- The API server
/metricsendpoint - Worker metrics
- PostgreSQL exporter (if configured)
- Redis exporter (if configured)
Configure Prometheus alerting rules in monitoring/prometheus/ and set up Grafana alert channels (email, Slack, webhook) for critical events:
- Worker offline for > 5 minutes
- Task failure rate > 10%
- CPU/RAM usage > 90%
- Database connection pool exhausted
- API response time > 5 seconds
# Check logs for a specific service
make logs-server
make logs-worker
docker compose logs postgres
docker compose logs redis
# Check health status
make status- Verify PostgreSQL is healthy:
docker compose ps postgres - Check credentials match between
.envand the server - Ensure the database volume exists:
docker volume ls | grep postgres - Try restarting:
docker compose restart postgres
- Check master is reachable from worker:
curl http://<master-ip>:8000/health - Verify
MASTER_URLin worker environment - Check worker logs:
docker compose logs worker - Ensure worker is approved in admin panel
- Verify Redis and MinIO are accessible from the worker
- Check which service is consuming memory:
docker stats - Review resource limits in
docker-compose.yml - Consider scaling workers across multiple machines
- Check for tasks stuck in
in_progressstate
- Verify at least one worker is online and approved
- Check that an agent is assigned and in
activestate - Review server logs for assignment errors
- Check Redis connectivity
- Verify API key is correct in
.env - Check API key has sufficient quota/credits
- Test key directly:
curl https://api.anthropic.com/v1/messages -H "x-api-key: $ANTHROPIC_API_KEY" ... - Check worker logs for specific error messages
- Try switching to a different LLM provider
- Increase
WORKER_MAX_AGENTSif workers have spare CPU/RAM - Add more workers for horizontal scaling
- Increase PostgreSQL
shared_buffersandwork_memfor large datasets - Enable Redis persistence if task queue reliability is critical
- Use SSDs for database and MinIO storage