Production Setup Guide
Complete guide for deploying Soothe in production environments.
Overview
This guide covers production deployment using Docker Compose (recommended), systemd for bare metal deployments, and Kubernetes for large-scale environments.
Prerequisites
System Requirements
Minimum (10 users, <50 threads):
- CPU: 2 cores
- RAM: 4 GB
- Storage: 20 GB SSD
- Network: Stable LAN connection
Recommended (50 users, <200 threads):
- CPU: 4 cores
- RAM: 8 GB
- Storage: 50 GB SSD
- Network: Dedicated database connection
Large Scale (500 users, >200 threads):
Software Requirements
- Docker 24.0+ and Docker Compose 2.20+
- PostgreSQL 17+ (or use Docker image)
- Python 3.11+ (for systemd deployment)
Network Requirements
- Database port (5432) accessible from daemon
- Daemon port (8765) accessible from clients (if WebSocket enabled)
- Outbound HTTPS for LLM provider APIs
Docker Compose Deployment (Recommended)
The fastest way to deploy Soothe in production.
Step 1: Prepare Configuration Files
# Navigate to deployment directory
cd soothe/deploy
# Create environment file
cp env-example .env
vim .env
Required environment variables:
# PostgreSQL credentials
POSTGRES_USER=postgres
POSTGRES_PASSWORD=<secure_password> # Generate with: openssl rand -base64 32
# LLM provider credentials
OPENAI_API_KEY=<your_openai_or_compatible_key>
OPENAI_BASE_URL=<your_openai_compatible_base_url> # Optional: for OpenAI-compatible providers
# Optional: OpenAI (if using official OpenAI API)
# OPENAI_API_KEY=<your_openai_key> # Only if using OpenAI models
# Connection strings (auto-generated)
SOOTHE_POSTGRES_BASE_DSN=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@soothe-pgvector:5432
SOOTHE_POSTGRES_VECTORS_DSN=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@soothe-pgvector:5432/soothe_vectors
Security: Never commit .env to version control. Use secrets management for production.
Step 2: Create Agent Configuration
cp config.prod.yml config.yml
vim config.yml # Optional customization
Default configuration (uses environment variables):
providers:
- name: openai-custom
provider_type: openai
api_base_url: "${OPENAI_BASE_URL}"
api_key: "${OPENAI_API_KEY}"
models:
- qwen-max
- qwen3.7-plus
- text-embedding-v3
router_profiles:
- name: production
router:
default: "openai-custom:qwen-max"
active_router_profile: production
embedding_profile:
- model_role: "openai-custom:text-embedding-v3"
embedding_dims: 1024
persistence:
default_backend: postgresql
postgres_base_dsn: "${SOOTHE_POSTGRES_BASE_DSN}"
vector_stores:
- name: pgvector
provider_type: pgvector
dsn: "${SOOTHE_POSTGRES_VECTORS_DSN}"
pool_size: 4
vector_store_router:
default: "pgvector:soothe_default"
Key points:
- Environment variables referenced with
${ENV_VAR} - PostgreSQL backend for production durability
- pgvector for vector similarity search
Step 3: Deploy Stack
# Start services
docker compose up -d
# Verify deployment
docker compose ps
# Expected output:
# NAME STATUS PORTS
# soothe-pgvector-1 Up (healthy) 127.0.0.1:5432->5432/tcp
# soothed-1 Up (healthy) 127.0.0.1:8765->8765/tcp
Step 4: Verify Database Initialization
Soothe auto-provisions PostgreSQL databases on first daemon startup when postgres_base_dsn is configured (RFC-802). No manual SQL init script is required for production.
# Start daemon and wait for provisioning (check logs)
docker compose logs soothed | grep -i "PostgreSQL database"
# Verify databases created
docker compose exec soothe-pgvector psql -U postgres -l
# Expected databases (after soothed has started):
# soothe_checkpoints | postgres | UTF8
# soothe_metadata | postgres | UTF8
# soothe_vectors | postgres | UTF8 | pgvector extension
# soothe_memory | postgres | UTF8
RFC-802 Multi-database architecture:
soothe_checkpoints: LangGraph + StrangeLoop statesoothe_metadata: Thread lifecycle metadatasoothe_vectors: Embedding vectors (pgvector)soothe_memory: Long-term semantic memory
Step 5: Verify Daemon Health
# Check daemon logs
docker compose logs soothed
# Test daemon connectivity (WebSocket)
soothe --daemon-host 127.0.0.1 --daemon-port 8765 -p "Hello" # Test WebSocket connectivity
Step 6: Test Full Stack
# Send test query
soothe -p "List all Python files in the workspace" # CLI auto-connects to daemon
# Verify thread created in database
docker compose exec soothe-pgvector psql -U postgres -d soothe_metadata \
-c "SELECT thread_id, status FROM threads LIMIT 5"
Production Docker Compose Configuration
Key settings (see deploy/docker-compose.yml for full file):
restart: unless-stopped— Auto-restart on failure- Health checks — Container health monitoring
- Volume mounts — Persistent data storage
- Config mount — Read-only configuration
- Workspace mount — Client workspace access (RFC-621)
Persistent Volumes
volumes:
soothe_postgres_data:
name: soothe_postgres_data
soothe_daemon_data:
name: soothe_daemon_data
Backup strategy:
# Backup PostgreSQL data
docker run --rm -v soothe_postgres_data:/data -v $(pwd):/backup \
alpine tar czf /backup/postgres_backup_$(date +%Y%m%d).tar.gz /data
# Backup daemon data
docker run --rm -v soothe_daemon_data:/data -v $(pwd):/backup \
alpine tar czf /backup/daemon_backup_$(date +%Y%m%d).tar.gz /data
See Backup Recovery for comprehensive backup strategies.
systemd Deployment (Bare Metal)
For environments without Docker or requiring direct hardware access.
Quick Setup
- Install PostgreSQL:
sudo apt install postgresql-17 postgresql-17-pgvector - Install Python 3.11+:
sudo apt install python3.11 python3.11-venv - Create user:
sudo useradd -r -s /bin/false soothe - Install Soothe:
sudo /opt/soothe/venv/bin/pip install soothe-daemon soothe - Configure: Create
/var/lib/soothe/config/config.ymland/etc/default/soothewith environment variables - Create systemd service: See
deploy/soothed.servicetemplate
systemd Service Template
Key settings (/etc/systemd/system/soothed.service):
User=soothe— Run as dedicated userEnvironmentFile=/etc/default/soothe— Load secrets from env fileExecStart=/opt/soothe/venv/bin/soothed start --foregroundRestart=on-failure— Auto-restart
See deploy/soothed.service for the full template with security hardening options.
Kubernetes Deployment
NoNewPrivileges=true PrivateTmp=true ProtectSystem=strict ProtectHome=true ReadWritePaths=/var/lib/soothe /var/log/soothe
Resource limits
LimitNOFILE=65536 MemoryMax=4G
[Install] WantedBy=multi-user.target
### Step 7: Enable and Start Service
```bash
# Reload systemd
sudo systemctl daemon-reload
# Enable service (auto-start on boot)
sudo systemctl enable soothed
# Start service
sudo systemctl start soothed
# Check status
sudo systemctl status soothed
# View logs
sudo journalctl -u soothed -f
systemd Best Practices
Resource limits:
# Memory limit (prevent OOM)
MemoryMax=4G
# CPU limit (if multiple services)
CPUQuota=50%
# File descriptor limit (for many connections)
LimitNOFILE=65536
Security hardening:
# Prevent privilege escalation
NoNewPrivileges=true
# Isolate filesystem
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
# Allow only specific paths
ReadWritePaths=/var/lib/soothe /var/log/soothe
Restart behavior:
# Restart on failure
Restart=on-failure
RestartSec=5s
# Or always restart (more aggressive)
Restart=always
RestartSec=10s
Kubernetes Deployment
For large-scale, high-availability deployments.
See Scaling Strategies for complete Kubernetes deployment guide including:
- StatefulSet for PostgreSQL
- Deployment for Soothe daemon
- Service and Ingress configuration
- Horizontal Pod Autoscaler
- ConfigMap and Secrets management
Network Configuration
Database Connection
PostgreSQL DSN format:
postgresql://user:password@host:port/database
Example:
persistence:
postgres_base_dsn: postgresql://postgres:secret@postgres-host:5432
Production recommendations:
- Use TLS for database connections (
sslmode=require) - Use connection pooling (psycopg pool)
- Set reasonable timeouts (
connect_timeout=10) - Use read replicas for query-heavy loads
Example with TLS:
persistence:
postgres_base_dsn: postgresql://user:pass@host:5432?sslmode=require&connect_timeout=10
Daemon Transports
Configure in ~/.soothe/config/daemon.yml:
WebSocket (local and remote):
transports:
websocket:
enabled: true
host: "127.0.0.1" # Bind to localhost; use reverse proxy for remote
port: 8765
cors_origins: ["https://your-app.com"]
Important: Use reverse proxy for remote WebSocket access. See Security Hardening.
Request timeouts
Long agent turns (multi-hour builds, large refactors) are bounded by thread_pool.request_timeout_seconds in daemon.yml (default 1209600 = 14 days). Autopilot goals use the parallel wall-clock knob agent.autopilot.goal_deadline_seconds in config.yml (same default). Set either to 0 / null only when you accept unbounded runs.
# daemon.yml
thread_pool:
request_timeout_seconds: 1209600 # 14d; 0 = no timeout
# config.yml
agent:
autopilot:
goal_deadline_seconds: 1209600 # 14d; null disables
Firewall Rules
Production firewall configuration:
# Allow PostgreSQL from daemon only (Docker internal network)
# No external PostgreSQL access needed
# Allow daemon WebSocket from reverse proxy
sudo ufw allow from 10.0.0.0/8 to any port 8765 proto tcp
# Block direct daemon access from external
sudo ufw deny 8765
# Allow reverse proxy HTTPS
sudo ufw allow 443/tcp
Workspace Mount Configuration (RFC-621)
For container deployments, workspace paths need mapping between host and container.
Docker Compose Workspace Mount
Compose bind-mounts $HOME by default (SOOTHE_WORKSPACE_HOST_ROOT optional):
services:
soothed:
volumes:
# Host workspace → container workspace (default source: $HOME)
- ${SOOTHE_WORKSPACE_HOST_ROOT:-${HOME}}:/var/lib/soothe/workspaces
Configuration Mapping
deploy/config.prod.yml resolves host_root from the same env var Compose injects (default $HOME):
workspace_mount:
host_root: ${SOOTHE_WORKSPACE_HOST_ROOT}
container_root: /var/lib/soothe/workspaces
How it works:
- Client sends workspace path:
/Users/yourname/Workspace/project1 - Daemon maps to container path:
/var/lib/soothe/workspaces/project1 - File operations work correctly in container
Production Workspace Strategies
Strategy 1: Shared workspace mount:
# Mount entire workspace directory
- /home/team/workspace:/var/lib/soothe/workspaces
Strategy 2: Per-project mounts:
# Mount specific projects
- /var/www/project-a:/var/lib/soothe/workspaces/project-a
- /var/www/project-b:/var/lib/soothe/workspaces/project-b
Strategy 3: Dynamic mounts (Kubernetes):
- Use PersistentVolumeClaims
- Mount PVCs to daemon pods
- See Scaling Strategies
Verification Checklist
After deployment, verify:
Database Connectivity
# Test PostgreSQL connection
psql -h postgres-host -U user -d soothe_checkpoints -c "SELECT 1"
# Verify pgvector extension
psql -h postgres-host -U user -d soothe_vectors -c "SELECT * FROM pg_extension WHERE extname='vector'"
# Check databases exist
psql -h postgres-host -U user -l | grep soothe
Daemon Health
# Check daemon status
soothed status
# Verify transports
soothed status --verbose
# Test WebSocket
curl http://localhost:8765/health
Configuration
# Verify environment variables
docker compose exec soothed env | grep SOOTHE
# Check config loaded
docker compose logs soothed | grep "config loaded"
Thread Creation
# Create test thread
soothe -p "Hello, this is a test" # CLI auto-connects to daemon
# Verify in database
psql -h postgres-host -U user -d soothe_metadata \
-c "SELECT thread_id, created_at FROM threads ORDER BY created_at DESC LIMIT 1"
Troubleshooting Production Deployment
PostgreSQL Connection Issues
Error: Connection refused or could not connect to server
Solution:
- Check PostgreSQL is running:
docker compose ps soothe-pgvector - Verify network:
docker compose exec soothe-pgvector ping soothe-pgvector - Check credentials in
.env - Verify firewall rules
pgvector Extension Missing
Error: extension "vector" must be installed
Solution:
# Install extension manually
docker compose exec soothe-pgvector psql -U postgres -d soothe_vectors \
-c "CREATE EXTENSION vector;"
# Verify extension
docker compose exec soothe-pgvector psql -U postgres -d soothe_vectors \
-c "SELECT * FROM pg_extension WHERE extname='vector'"
Daemon Won’t Start
Error: Daemon startup failed
Solution:
- Check logs:
docker compose logs soothed - Verify config.yml syntax:
python -c "import yaml; yaml.safe_load(open('config.yml'))" - Check environment variables:
docker compose config - Verify workspace mount exists
Workspace Access Denied
Error: Permission denied when accessing workspace
Solution:
# Check mount permissions
docker compose exec soothed ls -la /var/lib/soothe/workspaces
# Fix permissions on host
chmod -R a+rx /path/to/workspace
# Or use proper user mapping in Docker Compose
user: "1000:1000" # Match host user UID:GID
Production Configuration Examples
Example 1: Standard Production (OpenAI-Compatible + PostgreSQL)
providers:
- name: openai-custom
provider_type: openai
api_base_url: "${OPENAI_BASE_URL}"
api_key: "${OPENAI_API_KEY}"
models:
- qwen-max
- qwen3.7-plus
- text-embedding-v3
router_profiles:
- name: production
router:
default: "openai-custom:qwen-max"
fast: "openai-custom:qwen3.7-plus"
think: "openai-custom:qwen-max"
active_router_profile: production
embedding_profile:
- model_role: "openai-custom:text-embedding-v3"
embedding_dims: 1024
persistence:
default_backend: postgresql
postgres_base_dsn: "${SOOTHE_POSTGRES_BASE_DSN}"
postgres_pool_min_size: 8
checkpointer_pool_size: 24
vector_stores:
- name: pgvector
provider_type: pgvector
dsn: "${SOOTHE_POSTGRES_VECTORS_DSN}"
pool_size: 8
index_type: hnsw
observability:
log_file_path: /var/log/soothe/soothed.log
log_file_level: INFO
verbosity: normal
Example 2: Multi-Provider Production (OpenAI + Anthropic + PostgreSQL)
providers:
- name: openai
provider_type: openai
api_key: "${OPENAI_API_KEY}"
models:
- gpt-4o
- gpt-4o-mini
- o3-mini
- name: anthropic
provider_type: anthropic
api_key: "${ANTHROPIC_API_KEY}"
models:
- claude-sonnet-4-20250514
router_profiles:
- name: production
router:
default: "openai:gpt-4o-mini"
think: "anthropic:claude-sonnet-4-20250514"
fast: "openai:gpt-4o-mini"
image: "openai:gpt-4o"
active_router_profile: production
embedding_profile:
- model_role: "openai:text-embedding-3-small"
embedding_dims: 1536
persistence:
default_backend: postgresql
postgres_base_dsn: "${POSTGRES_DSN}"
Example 3: Secure Production (Strict Security Policy)
providers:
- name: openai
provider_type: openai
api_key: "${OPENAI_API_KEY}"
models:
- gpt-4o-mini
persistence:
default_backend: postgresql
security:
allow_paths_outside_workspace: false
require_approval_for_outside_paths: true
denied_paths:
- /etc/**
- /bin/**
- /usr/**
- ~/.ssh/**
- ~/.aws/**
- '**/.env'
- '**/secrets.json'
denied_file_types:
- .key
- .pem
- .p12
require_approval_for_file_types:
- .env
- .credentials
agent:
autonomous:
enabled_by_default: false
max_iterations: 15
max_parallel_goals: 2
Next Steps
After successful production deployment:
- Monitoring: Configure observability → Monitoring Guide
- Security: Harden deployment → Security Hardening
- Scaling: Plan for growth → Scaling Strategies
- Backup: Protect data → Backup Recovery
Related Documentation
- Deployment Guide Overview - Deployment architecture overview
- Docker Compose Reference - Production stack definition
- Configuration Guide - Complete YAML reference
- Daemon Management - Daemon lifecycle commands
- Multi-Transport - Transport configuration
- Authentication - Reverse proxy authentication
Questions? Check Troubleshooting or the Production Deployment README.