Skip to main content

Behavry — Self-Hosted Deployment Guide

This guide covers deploying the full Behavry stack inside your own infrastructure (on-prem or a private cloud VPC). When self-hosted, no agent data, tool call content, or audit logs ever leave your network.


Prerequisites

RequirementMinimum
Docker Engine24.0+
Docker Compose2.20+
CPU2 vCPU
RAM4 GB
Disk20 GB (grows with audit volume)
Outbound internetOnly if agents call external APIs

Quick Start

# 1. Clone the repository
git clone https://github.com/your-org/behavry.git
cd behavry

# 2. Copy the example environment file and fill in secrets
cp docker/.env.example docker/.env
$EDITOR docker/.env

# 3. Build and start all services
make build
make up

# 4. Run initial database migration
make migrate

# 5. Verify health
curl http://localhost:8000/health/ready

The dashboard is available at http://localhost:3000 (or your configured domain).


Environment Variables

All configuration is set in docker/.env. The table below shows every variable with its default and whether it is required for production.

Core

VariableDefaultRequiredDescription
BEHAVRY_ENVdevelopmentNodevelopment or production
BEHAVRY_DB_URLpostgresql+asyncpg://behavry:behavry@db:5432/behavryYesPostgreSQL connection string
BEHAVRY_OPA_URLhttp://opa:8181NoOPA sidecar URL (use Docker service name)
BEHAVRY_ADMIN_USERNAMEadminNoInitial admin account username
BEHAVRY_ADMIN_PASSWORDYesInitial admin account password
SECRET_KEYYes (prod)32-byte random secret for session signing

JWT / Auth

VariableDefaultRequiredDescription
BEHAVRY_JWT_PRIVATE_KEYAuto-generatedYes (prod)RS256 private key (PEM)
BEHAVRY_JWT_PUBLIC_KEYAuto-generatedYes (prod)RS256 public key (PEM)
BEHAVRY_JWT_ISSUERbehavryNoJWT iss claim value

In development, Behavry auto-generates an RS256 key pair on first startup and writes it to a Docker volume. In production, generate your own keys and supply them explicitly:

# Generate RS256 key pair
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem

# Add to docker/.env
BEHAVRY_JWT_PRIVATE_KEY="$(cat private.pem)"
BEHAVRY_JWT_PUBLIC_KEY="$(cat public.pem)"

Observability (optional)

VariableDefaultRequiredDescription
SENTRY_DSNNoSentry DSN for error tracking
BEHAVRY_METRICS_TOKENNoBearer token protecting /metrics endpoint
BUILD_SHANoGit SHA baked in at build time; shown in Settings

Proxy Targets

VariableDefaultRequiredDescription
BEHAVRY_OLLAMA_URLhttp://localhost:11434NoOllama URL for on-prem Ollama proxy

TLS / HTTPS

Behavry's nginx container (docker/nginx.conf) serves the backend and dashboard. For production, supply TLS certificates and update nginx.conf:

server {
listen 443 ssl;
server_name behavry.yourdomain.com;

ssl_certificate /etc/nginx/certs/behavry.crt;
ssl_certificate_key /etc/nginx/certs/behavry.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;

location / {
proxy_pass http://dashboard:3000;
}

location /api/ {
proxy_pass http://backend:8000;
}

location /mcp/ {
proxy_pass http://backend:8000;
proxy_buffering off; # Required for SSE streaming
}
}

Mount your certs into the nginx container by adding to docker-compose.yml:

nginx:
volumes:
- ./certs/behavry.crt:/etc/nginx/certs/behavry.crt:ro
- ./certs/behavry.key:/etc/nginx/certs/behavry.key:ro
# Install certbot and obtain a certificate
apt install certbot
certbot certonly --standalone -d behavry.yourdomain.com

# Certificates are written to:
# /etc/letsencrypt/live/behavry.yourdomain.com/fullchain.pem
# /etc/letsencrypt/live/behavry.yourdomain.com/privkey.pem

# Mount them into the nginx container (update docker-compose.yml paths accordingly)

Network Isolation

Behavry runs entirely on a Docker bridge network (behavry_net). No service is exposed externally except nginx (port 80/443). The database and OPA sidecar are internal-only.

For a production VPC deployment, the recommended topology is:

Internet
↓ (TCP 443 only)
Load Balancer / WAF

nginx (TLS termination)
↓ (internal only)
┌──────────────────────────────────────────────────────┐
│ Docker bridge network (behavry_net) │
│ backend:8000 │ opa:8181 │ db:5432 │
└──────────────────────────────────────────────────────┘

Firewall rules (minimum):

DirectionPortProtocolPurpose
Inbound443TCPHTTPS from agents, browser extension, dashboard
Inbound80TCPHTTP → redirect to HTTPS
Outbound443TCPAgent tool calls to external APIs (if applicable)
InternalAllDocker bridge (no external exposure)

Browser Extension Configuration

The Behavry browser extension must be pointed at your self-hosted instance. After deploying:

  1. Open the Behavry extension popup → click the settings gear icon
  2. Set Server URL to https://behavry.yourdomain.com
  3. In the Behavry dashboard, navigate to Settings → Extension Tokens
  4. Click Generate Token, copy the plaintext token (shown once)
  5. Paste the token into the extension popup → Extension Token field
  6. Click Save — the extension stores the token in chrome.storage.local

The extension authenticates every event POST with X-Extension-Token. Tokens expire after 90 days; rotate them from the Extension Tokens settings page.


Agent SDK Configuration

Point the Python SDK at your self-hosted instance:

import behavry

client = behavry.Client(
server_url="https://behavry.yourdomain.com",
client_id="your-agent-client-id",
client_secret="your-agent-client-secret",
)

Or via environment variables:

export BEHAVRY_SERVER_URL=https://behavry.yourdomain.com
export BEHAVRY_CLIENT_ID=your-agent-client-id
export BEHAVRY_CLIENT_SECRET=your-agent-client-secret

Database Backup

Behavry uses PostgreSQL 16 with TimescaleDB. The audit log (audit_events) is a hypertable partitioned by timestamp — standard pg_dump works correctly.

# Dump (inside docker-compose project directory)
docker compose exec db pg_dump -U behavry behavry > backup_$(date +%Y%m%d).sql

# Restore
docker compose exec -T db psql -U behavry behavry < backup_20260101.sql

For continuous backup, use pg_basebackup or a managed backup tool (Barman, pgBackRest). TimescaleDB Community Edition supports timescaledb-backup for hypertable-aware backups.


Upgrading

# Pull latest code
git pull

# Rebuild images
make build

# Apply new migrations (always before restart)
make migrate

# Restart services
make down && make up

Migrations are applied by the dedicated migrate container before the backend starts. If a migration fails, the backend will refuse to start and log the error.

Demo data after upgrade

If you run the demo simulators after upgrading and see Auth failed: Invalid credentials, the demo agents exist in the DB but their credentials were generated in a previous run and are not stored in .creds/. Re-seed with --wipe to regenerate them:

make seed-wipe-docker    # or:
docker compose -f docker/docker-compose.yml exec backend python /app/demos/setup/seed.py --wipe

This deletes existing demo agents, recreates them with fresh credentials, and saves the new credentials to demos/data/.creds/ for the simulators to use.


Health Endpoints

EndpointDescription
GET /health/liveLiveness — returns 200 if the process is running
GET /health/readyReadiness — checks DB, OPA, escalation queue, and event bus
GET /metricsPrometheus metrics (optionally token-protected)

The ready endpoint returns a JSON object with per-component status. Use it in your load balancer health check.


Security Hardening Checklist

  • BEHAVRY_ENV=production in docker/.env
  • BEHAVRY_ADMIN_PASSWORD set to a strong random value
  • BEHAVRY_JWT_PRIVATE_KEY / BEHAVRY_JWT_PUBLIC_KEY set from generated keys (not auto-generated)
  • SECRET_KEY set to a 32-byte random value
  • TLS enabled on nginx with valid certificate
  • Port 8000 (backend) and 5432 (database) NOT exposed to the internet
  • BEHAVRY_METRICS_TOKEN set if /metrics is accessible externally
  • Extension token generated and configured in the browser extension
  • Database backup scheduled

Troubleshooting

Backend won't start — migration error

ERROR: relation "audit_events" does not exist

Run make migrate before make up. Migrations must complete before the backend starts.

Extension events rejected with 401 Extension token is missing, expired, or incorrect. Regenerate from Settings → Extension Tokens and update the token in the extension popup.

OPA policy evaluation returning 500 Check OPA is healthy: curl http://localhost:8181/health. If OPA is down, the backend rejects all agent requests. Check docker compose logs opa.

TimescaleDB hypertable warnings If you see WARNING: could not create hypertable on first run, the TimescaleDB extension may not be loaded. Ensure you're using the timescale/timescaledb-ha image and that shared_preload_libraries = 'timescaledb' is in postgresql.conf.