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

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 state
  • soothe_metadata: Thread lifecycle metadata
  • soothe_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

  1. Install PostgreSQL: sudo apt install postgresql-17 postgresql-17-pgvector
  2. Install Python 3.11+: sudo apt install python3.11 python3.11-venv
  3. Create user: sudo useradd -r -s /bin/false soothe
  4. Install Soothe: sudo /opt/soothe/venv/bin/pip install soothe-daemon soothe
  5. Configure: Create /var/lib/soothe/config/config.yml and /etc/default/soothe with environment variables
  6. Create systemd service: See deploy/soothed.service template

systemd Service Template

Key settings (/etc/systemd/system/soothed.service):

  • User=soothe — Run as dedicated user
  • EnvironmentFile=/etc/default/soothe — Load secrets from env file
  • ExecStart=/opt/soothe/venv/bin/soothed start --foreground
  • Restart=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):

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:

  1. Check PostgreSQL is running: docker compose ps soothe-pgvector
  2. Verify network: docker compose exec soothe-pgvector ping soothe-pgvector
  3. Check credentials in .env
  4. 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:

  1. Check logs: docker compose logs soothed
  2. Verify config.yml syntax: python -c "import yaml; yaml.safe_load(open('config.yml'))"
  3. Check environment variables: docker compose config
  4. 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:

  1. Monitoring: Configure observability → Monitoring Guide
  2. Security: Harden deployment → Security Hardening
  3. Scaling: Plan for growth → Scaling Strategies
  4. Backup: Protect data → Backup Recovery

Questions? Check Troubleshooting or the Production Deployment README.