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
| Requirement | Minimum |
|---|---|
| Docker Engine | 24.0+ |
| Docker Compose | 2.20+ |
| CPU | 2 vCPU |
| RAM | 4 GB |
| Disk | 20 GB (grows with audit volume) |
| Outbound internet | Only 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
| Variable | Default | Required | Description |
|---|---|---|---|
BEHAVRY_ENV | development | No | development or production |
BEHAVRY_DB_URL | postgresql+asyncpg://behavry:behavry@db:5432/behavry | Yes | PostgreSQL connection string |
BEHAVRY_OPA_URL | http://opa:8181 | No | OPA sidecar URL (use Docker service name) |
BEHAVRY_ADMIN_USERNAME | admin | No | Initial admin account username |
BEHAVRY_ADMIN_PASSWORD | — | Yes | Initial admin account password |
SECRET_KEY | — | Yes (prod) | 32-byte random secret for session signing |
JWT / Auth
| Variable | Default | Required | Description |
|---|---|---|---|
BEHAVRY_JWT_PRIVATE_KEY | Auto-generated | Yes (prod) | RS256 private key (PEM) |
BEHAVRY_JWT_PUBLIC_KEY | Auto-generated | Yes (prod) | RS256 public key (PEM) |
BEHAVRY_JWT_ISSUER | behavry | No | JWT 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)
| Variable | Default | Required | Description |
|---|---|---|---|
SENTRY_DSN | — | No | Sentry DSN for error tracking |
BEHAVRY_METRICS_TOKEN | — | No | Bearer token protecting /metrics endpoint |
BUILD_SHA | — | No | Git SHA baked in at build time; shown in Settings |
Proxy Targets
| Variable | Default | Required | Description |
|---|---|---|---|
BEHAVRY_OLLAMA_URL | http://localhost:11434 | No | Ollama 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
Let's Encrypt (recommended for VPS deployments)
# 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):
| Direction | Port | Protocol | Purpose |
|---|---|---|---|
| Inbound | 443 | TCP | HTTPS from agents, browser extension, dashboard |
| Inbound | 80 | TCP | HTTP → redirect to HTTPS |
| Outbound | 443 | TCP | Agent tool calls to external APIs (if applicable) |
| Internal | All | — | Docker bridge (no external exposure) |
Browser Extension Configuration
The Behavry browser extension must be pointed at your self-hosted instance. After deploying:
- Open the Behavry extension popup → click the settings gear icon
- Set Server URL to
https://behavry.yourdomain.com - In the Behavry dashboard, navigate to Settings → Extension Tokens
- Click Generate Token, copy the plaintext token (shown once)
- Paste the token into the extension popup → Extension Token field
- 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
| Endpoint | Description |
|---|---|
GET /health/live | Liveness — returns 200 if the process is running |
GET /health/ready | Readiness — checks DB, OPA, escalation queue, and event bus |
GET /metrics | Prometheus 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=productionindocker/.env -
BEHAVRY_ADMIN_PASSWORDset to a strong random value -
BEHAVRY_JWT_PRIVATE_KEY/BEHAVRY_JWT_PUBLIC_KEYset from generated keys (not auto-generated) -
SECRET_KEYset 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_TOKENset if/metricsis 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.