Self-Hosting Guide

Deploy Agent OTP on your own infrastructure. Full control over your data, compliance with regulations, and air-gapped deployments.

Prerequisites

  • Docker and Docker Compose
  • PostgreSQL 15+ (or use included container)
  • Redis 7+ (or use included container)
  • Domain with SSL certificate (for production)

Quick Start with Docker

# Clone the repository
git clone https://github.com/orristech/agent-otp.git
cd agent-otp

# Copy environment template
cp .env.example .env

# Edit environment variables
nano .env

# Start all services
docker compose up -d

# Check status
docker compose ps

Environment Configuration

# .env file

# Database
DATABASE_URL=postgresql://postgres:password@db:5432/agent_otp
POSTGRES_PASSWORD=your-secure-password

# Redis
REDIS_URL=redis://redis:6379

# API Configuration
API_PORT=3000
API_SECRET_KEY=your-32-character-secret-key
JWT_SECRET=your-jwt-secret

# Dashboard
DASHBOARD_URL=https://otp.your-company.com
NEXT_PUBLIC_API_URL=https://api.otp.your-company.com

# Telegram Bot (optional)
TELEGRAM_BOT_TOKEN=your-bot-token

# Email Notifications (optional)
SMTP_HOST=smtp.your-company.com
SMTP_PORT=587
SMTP_USER=notifications@your-company.com
SMTP_PASSWORD=your-smtp-password
EMAIL_FROM=notifications@your-company.com

# Encryption
ENCRYPTION_KEY=your-32-byte-encryption-key

Docker Compose Configuration

# docker-compose.yml
version: '3.8'

services:
  api:
    image: ghcr.io/orristech/agent-otp-api:latest
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - REDIS_URL=${REDIS_URL}
      - API_SECRET_KEY=${API_SECRET_KEY}
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      - db
      - redis
    restart: unless-stopped

  dashboard:
    image: ghcr.io/orristech/agent-otp-dashboard:latest
    ports:
      - "3001:3000"
    environment:
      - NEXT_PUBLIC_API_URL=${API_URL}
    restart: unless-stopped

  telegram-bot:
    image: ghcr.io/orristech/agent-otp-telegram:latest
    environment:
      - API_URL=http://api:3000
      - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
    depends_on:
      - api
    restart: unless-stopped

  db:
    image: postgres:15-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=agent_otp
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:

Database Setup

Run migrations after starting the database:

# Apply database migrations
docker compose exec api bun run db:migrate

# Seed initial data (optional)
docker compose exec api bun run db:seed

Kubernetes Deployment

For production Kubernetes deployments:

# Add the Helm repository
helm repo add agent-otp https://charts.agentotp.com
helm repo update

# Create namespace
kubectl create namespace agent-otp

# Create secrets
kubectl create secret generic agent-otp-secrets \
  --namespace agent-otp \
  --from-literal=database-url='postgresql://...' \
  --from-literal=redis-url='redis://...' \
  --from-literal=api-secret='...'

# Install with Helm
helm install agent-otp agent-otp/agent-otp \
  --namespace agent-otp \
  --values values.yaml

Helm Values Example

# values.yaml
api:
  replicas: 3
  resources:
    requests:
      memory: "256Mi"
      cpu: "250m"
    limits:
      memory: "512Mi"
      cpu: "500m"

dashboard:
  replicas: 2
  ingress:
    enabled: true
    host: otp.your-company.com
    tls: true

postgresql:
  enabled: false  # Use external database

redis:
  enabled: true
  cluster:
    enabled: true

ingress:
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod

SSL/TLS Configuration

With Let's Encrypt (Caddy)

# Caddyfile
api.otp.your-company.com {
  reverse_proxy api:3000
}

otp.your-company.com {
  reverse_proxy dashboard:3000
}

With nginx

server {
    listen 443 ssl http2;
    server_name api.otp.your-company.com;

    ssl_certificate /etc/letsencrypt/live/api.otp.your-company.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.otp.your-company.com/privkey.pem;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

High Availability

For production deployments requiring high availability:

  • API - Deploy 3+ instances behind a load balancer
  • PostgreSQL - Use a managed service or replica set
  • Redis - Use Redis Cluster or Sentinel
  • Telegram Bot - Single instance with failover

Backup and Recovery

# Backup database
docker compose exec db pg_dump -U postgres agent_otp > backup.sql

# Restore database
docker compose exec -T db psql -U postgres agent_otp < backup.sql

# Backup Redis
docker compose exec redis redis-cli BGSAVE

# Automated backup script
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
docker compose exec -T db pg_dump -U postgres agent_otp | gzip > "backup_${DATE}.sql.gz"
aws s3 cp "backup_${DATE}.sql.gz" s3://your-backup-bucket/

Monitoring

The API exposes Prometheus metrics:

# Metrics endpoint
curl http://localhost:3000/metrics

# Example metrics
agent_otp_permissions_total{status="approved"} 1234
agent_otp_permissions_total{status="denied"} 56
agent_otp_token_usage_total 1180
agent_otp_api_latency_seconds{quantile="0.99"} 0.045

Grafana Dashboard

Import the official Grafana dashboard:

# Dashboard ID: 12345
# Or import from: https://grafana.com/grafana/dashboards/12345

Security Hardening

  • Network isolation - Put database and Redis on private network
  • Secrets management - Use Vault, AWS Secrets Manager, or similar
  • Audit logging - Forward logs to SIEM system
  • Encryption at rest - Enable database encryption
  • Rate limiting - Configure per-IP rate limits

Updating

# Pull latest images
docker compose pull

# Restart services with zero downtime
docker compose up -d --no-deps api
docker compose up -d --no-deps dashboard

# Run any new migrations
docker compose exec api bun run db:migrate

See Also