Skip to main content
The Kubiya Control Plane can be self-hosted on your own infrastructure, giving you full control over your agent orchestration platform with VPC-only access, compliance boundaries, and custom integrations.
Run Anywhere: Deploy on local machines, cloud VMs (AWS, GCP, Azure), Kubernetes clusters, or bare metal servers with full access to private resources.

Overview

The Control Plane is a FastAPI-based REST API server that orchestrates your entire Kubiya infrastructure. Key Responsibilities:
  • Manages agent lifecycle and configuration
  • Integrates with Temporal for workflow orchestration
  • Handles teams, projects, environments, and policies
  • Streams real-time execution events via WebSocket
  • Routes tasks to appropriate queues and workers

Quick Start

1

Create Database

createdb kubiya_control_plane
2

Configure Connection

export DATABASE_URL='postgresql://user:pass@localhost:5432/kubiya_control_plane'
URL-encode special characters: !%21, @%40, #%23
3

Start Server

kubiya control-plane start
Automatically installs the latest version from PyPI.
4

Verify

curl http://localhost:7777/api/health
Visit http://localhost:7777/api/docs for interactive API documentation.

Prerequisites

System Requirements

  • Python 3.10+ (NOT 3.8+)
  • PostgreSQL 13+ (required)
  • Redis (optional, recommended)
  • 2GB RAM minimum (4GB recommended)

Database Setup

# macOS
brew install postgresql@15
brew services start postgresql@15

# Ubuntu/Debian
sudo apt-get install postgresql-15
sudo systemctl start postgresql

# Create database
createdb kubiya_control_plane

Optional: Redis

# macOS
brew install redis && brew services start redis

# Ubuntu/Debian
sudo apt-get install redis-server && sudo systemctl start redis

# Docker
docker run -d -p 6379:6379 redis:7-alpine

Configuration

Package Installation

By default, the CLI installs the latest version from PyPI automatically.
# Latest version
kubiya control-plane start

# Specific version
kubiya control-plane start --package-version=0.6.0

Server Options

# Default configuration
kubiya control-plane start

# Custom host/port
kubiya control-plane start --host=127.0.0.1 --port=8080

# Production with 4 workers
kubiya control-plane start --workers=4

# Development with hot reload
kubiya control-plane start --development

# With LiteLLM gateway
kubiya control-plane start --litellm-gateway-url=http://localhost:4000
Development Mode (--development):
  • Hot reloading on code changes
  • Single worker process
  • NOT for production
Production Mode (--workers=N):
  • Gunicorn with multiple workers
  • Better performance and fault tolerance
  • Recommended: 4 workers
Route LLM requests through your own proxy:
kubiya control-plane start \
  --litellm-gateway-url=http://localhost:4000 \
  --litellm-api-key=your-key
Benefits: centralized management, cost tracking, rate limiting

Environment Variables

VariableRequiredDefaultDescription
DATABASE_URL-PostgreSQL connection string
API_HOST0.0.0.0Server bind address
API_PORT7777Server port
SECRET_KEY⚠️AutoJWT secret (set for production!)
REDIS_URLredis://localhost:6379/0Redis connection
TEMPORAL_HOSTlocalhost:7233Temporal server
LITELLM_API_KEY-LiteLLM API key
LITELLM_GATEWAY_URL-LiteLLM gateway URL
SUPABASE_URL-Supabase project URL
SUPABASE_SERVICE_KEY-Supabase service key
Use CLI flags --workers and --development for worker count and hot reloading.
Production Security: Never use auto-generated SECRET_KEY in production!
export SECRET_KEY="$(openssl rand -base64 32)"

Production Deployment

Docker

FROM python:3.11-slim
RUN curl -fsSL https://cli.kubiya.ai/install.sh | sh
WORKDIR /app

ENV API_PORT=7777
HEALTHCHECK CMD curl -f http://localhost:7777/api/health || exit 1

CMD ["kubiya", "control-plane", "start"]

Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata:
  name: control-plane
spec:
  replicas: 3
  selector:
    matchLabels:
      app: control-plane
  template:
    metadata:
      labels:
        app: control-plane
    spec:
      containers:
      - name: control-plane
        image: your-registry/control-plane:latest
        ports:
        - containerPort: 7777
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: control-plane-secrets
              key: database-url
        - name: SECRET_KEY
          valueFrom:
            secretKeyRef:
              name: control-plane-secrets
              key: secret-key
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "2Gi"
            cpu: "1000m"
        livenessProbe:
          httpGet:
            path: /api/health
            port: 7777
          initialDelaySeconds: 60
        readinessProbe:
          httpGet:
            path: /api/health
            port: 7777
          initialDelaySeconds: 30

Nginx Reverse Proxy

upstream control_plane {
    server localhost:7777;
}

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

    ssl_certificate /etc/ssl/certs/your-domain.crt;
    ssl_certificate_key /etc/ssl/private/your-domain.key;
    ssl_protocols TLSv1.2 TLSv1.3;

    location / {
        proxy_pass http://control_plane;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Database Management

Automatic Migrations

Migrations run automatically on startup using Alembic:
kubiya control-plane start
# [3/4] Running database migrations...
# ✓ Database migrations complete

Backup & Restore

# Backup
pg_dump $DATABASE_URL > backup_$(date +%Y%m%d_%H%M%S).sql

# Restore
psql $DATABASE_URL < backup_20260105_140000.sql

# Automated backups (cron)
0 2 * * * pg_dump $DATABASE_URL | gzip > /backups/cp_$(date +\%Y\%m\%d).sql.gz

Connection Pooling

Use PgBouncer for production:
[databases]
kubiya = host=localhost port=5432 dbname=kubiya

[pgbouncer]
listen_port = 6432
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25

Monitoring

Health Checks

curl http://localhost:7777/api/health
# {"status":"ok"}

API Documentation

Once running, access:
  • Swagger UI: http://localhost:7777/api/docs
  • ReDoc: http://localhost:7777/api/redoc
  • OpenAPI Spec: http://localhost:7777/api/openapi.json

Built-in WebUI

# Default (auto-assigned port)
kubiya control-plane start

# Custom port
kubiya control-plane start --webui-port=9999

# Disable
kubiya control-plane start --no-webui
The WebUI provides real-time logs, health status, and process info.

Logs

tail -f ~/.kubiya/control-plane/logs/server.log
cat ~/.kubiya/control-plane/logs/server.log | jq '.message'

Security

Production Checklist

Best Practices

Never use auto-generated keys in production:
# Generate strong key
export SECRET_KEY="$(openssl rand -base64 32)"

# Use secret managers
export SECRET_KEY="$(aws secretsmanager get-secret-value \
  --secret-id kubiya/control-plane/secret-key \
  --query SecretString --output text)"
Never pass secrets via CLI flags (visible in process list)
Always use SSL/TLS:
export DATABASE_URL='postgresql://user:pass@host:5432/db?sslmode=require'
Grant minimal permissions:
CREATE USER kubiya_cp WITH PASSWORD 'secure_password';
GRANT CONNECT ON DATABASE kubiya TO kubiya_cp;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO kubiya_cp;
Bind to localhost for local-only access:
kubiya control-plane start --host=127.0.0.1
Always use reverse proxy for external access with:
  • SSL/TLS termination
  • Rate limiting
  • WAF protection

Troubleshooting

Error: migration failed: connection refusedSolutions:
  1. Verify PostgreSQL is running: pg_isready -h localhost -p 5432
  2. Check DATABASE_URL format: echo $DATABASE_URL
  3. Test connection: psql $DATABASE_URL -c "SELECT 1"
  4. Check firewall: nc -zv postgres-host 5432
Error: server did not become healthySolutions:
  1. Find process: lsof -i:7777
  2. Kill process: lsof -ti:7777 | xargs kill
  3. Use different port: --port=8888
Error: could not translate host nameCause: Special characters in password not encodedSolution: Encode special characters:
  • !%21, @%40, #%23, $%24, %%25
# Wrong
DATABASE_URL='postgresql://user:pass!word@host:5432/db'

# Correct
DATABASE_URL='postgresql://user:pass%21word@host:5432/db'
Or use Python: python3 -c "from urllib.parse import quote; print(quote('pass!word'))"
Error: ModuleNotFoundErrorSolutions:
  1. Reinstall: kubiya control-plane start --no-cache
  2. Specific version: kubiya control-plane start --package-version=0.6.0 --no-cache
  3. Use Git: --package-source=git+https://github.com/kubiyabot/control-plane-api.git@main
Warning: Using auto-generated SECRET_KEY (not suitable for production)Solution:
export SECRET_KEY="$(openssl rand -base64 32)"
kubiya control-plane start

Debug Mode

export LOG_LEVEL=DEBUG
kubiya control-plane start
tail -f ~/.kubiya/control-plane/logs/server.log | jq .

Next Steps

Deploy Workers

Set up workers to execute agent workflows

Configure LLM Gateway

Route models through your own LiteLLM proxy

Create Agents

Build and configure AI agents

API Reference

Explore the full Control Plane API