> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kubiya.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Control Plane Self-Hosting

> Deploy and manage the Kubiya Control Plane API server on your own infrastructure

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.

<Tip>
  **Run Anywhere**: Deploy on local machines, cloud VMs (AWS, GCP, Azure), Kubernetes clusters, or bare metal servers with full access to private resources.
</Tip>

## 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

```mermaid theme={null}
graph TB
    CLI[CLI/SDK/Clients] --> CP[Control Plane]
    CP --> DB[(PostgreSQL)]
    CP --> Redis[(Redis)]
    CP --> Temporal[Temporal]
    Temporal --> Workers[Workers]

    style CP fill:#c084fc,stroke:#9333ea,color:#000
    style DB fill:#3b82f6,stroke:#1e40af,color:#fff
    style Redis fill:#dc2626,stroke:#991b1b,color:#fff
    style Temporal fill:#10b981,stroke:#047857,color:#fff
```

## Quick Start

<Steps>
  <Step title="Create Database">
    ```bash theme={null}
    createdb kubiya_control_plane
    ```
  </Step>

  <Step title="Configure Connection">
    ```bash theme={null}
    export DATABASE_URL='postgresql://user:pass@localhost:5432/kubiya_control_plane'
    ```

    <Warning>
      **URL-encode special characters**: `!` → `%21`, `@` → `%40`, `#` → `%23`
    </Warning>
  </Step>

  <Step title="Start Server">
    ```bash theme={null}
    kubiya control-plane start
    ```

    Automatically installs the latest version from PyPI.
  </Step>

  <Step title="Verify">
    ```bash theme={null}
    curl http://localhost:7777/api/health
    ```

    Visit `http://localhost:7777/api/docs` for interactive API documentation.
  </Step>
</Steps>

## Prerequisites

### System Requirements

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

### Database Setup

<Tabs>
  <Tab title="Local PostgreSQL">
    ```bash theme={null}
    # 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
    ```
  </Tab>

  <Tab title="Supabase">
    Use your Supabase connection string with proper URL encoding:

    ```bash theme={null}
    export DATABASE_URL='postgresql://postgres.xyz:pass%21@aws-0-us-east-1.pooler.supabase.com:6543/postgres'
    ```
  </Tab>

  <Tab title="AWS RDS">
    ```bash theme={null}
    export DATABASE_URL='postgresql://user:pass@mydb.xyz.rds.amazonaws.com:5432/kubiya?sslmode=require'
    ```
  </Tab>

  <Tab title="Google Cloud SQL">
    ```bash theme={null}
    export DATABASE_URL='postgresql://user:pass@/kubiya?host=/cloudsql/project:region:instance'
    ```
  </Tab>
</Tabs>

### Optional: Redis

```bash theme={null}
# 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.

<Tabs>
  <Tab title="PyPI (Default)">
    ```bash theme={null}
    # Latest version
    kubiya control-plane start

    # Specific version
    kubiya control-plane start --package-version=0.6.0
    ```
  </Tab>

  <Tab title="Git Repository">
    ```bash theme={null}
    # Main branch
    kubiya control-plane start \
      --package-source=git+https://github.com/kubiyabot/control-plane-api.git@main

    # Specific commit
    kubiya control-plane start \
      --package-source=kubiyabot/control-plane-api@abc123
    ```
  </Tab>

  <Tab title="Local Source">
    ```bash theme={null}
    kubiya control-plane start --package-source=/path/to/control-plane-api
    ```
  </Tab>
</Tabs>

### Server Options

```bash theme={null}
# 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
```

<AccordionGroup>
  <Accordion title="Production vs Development" icon="code">
    **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
  </Accordion>

  <Accordion title="LiteLLM Gateway" icon="route">
    Route LLM requests through your own proxy:

    ```bash theme={null}
    kubiya control-plane start \
      --litellm-gateway-url=http://localhost:4000 \
      --litellm-api-key=your-key
    ```

    Benefits: centralized management, cost tracking, rate limiting
  </Accordion>
</AccordionGroup>

### Environment Variables

| Variable               | Required | Default                    | Description                      |
| ---------------------- | -------- | -------------------------- | -------------------------------- |
| `DATABASE_URL`         | ✅        | -                          | PostgreSQL connection string     |
| `API_HOST`             | ❌        | `0.0.0.0`                  | Server bind address              |
| `API_PORT`             | ❌        | `7777`                     | Server port                      |
| `SECRET_KEY`           | ⚠️       | Auto                       | JWT secret (set for production!) |
| `REDIS_URL`            | ❌        | `redis://localhost:6379/0` | Redis connection                 |
| `TEMPORAL_HOST`        | ❌        | `localhost:7233`           | Temporal server                  |
| `LITELLM_API_KEY`      | ❌        | -                          | LiteLLM API key                  |
| `LITELLM_GATEWAY_URL`  | ❌        | -                          | LiteLLM gateway URL              |
| `SUPABASE_URL`         | ❌        | -                          | Supabase project URL             |
| `SUPABASE_SERVICE_KEY` | ❌        | -                          | Supabase service key             |

<Note>
  Use CLI flags `--workers` and `--development` for worker count and hot reloading.
</Note>

<Warning>
  **Production Security**: Never use auto-generated `SECRET_KEY` in production!

  ```bash theme={null}
  export SECRET_KEY="$(openssl rand -base64 32)"
  ```
</Warning>

## Production Deployment

### Docker

<Tabs>
  <Tab title="Dockerfile">
    ```dockerfile theme={null}
    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"]
    ```
  </Tab>

  <Tab title="Docker Compose">
    ```yaml theme={null}
    version: '3.8'
    services:
      postgres:
        image: postgres:15-alpine
        environment:
          POSTGRES_DB: kubiya
          POSTGRES_USER: kubiya
          POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
        volumes:
          - postgres_data:/var/lib/postgresql/data
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U kubiya"]
          interval: 10s

      redis:
        image: redis:7-alpine
        command: redis-server --requirepass ${REDIS_PASSWORD}

      control-plane:
        build: .
        depends_on:
          postgres:
            condition: service_healthy
        environment:
          DATABASE_URL: postgresql://kubiya:${POSTGRES_PASSWORD}@postgres:5432/kubiya
          REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/0
          SECRET_KEY: ${SECRET_KEY}
        ports:
          - "7777:7777"
        restart: unless-stopped
        command: ["kubiya", "control-plane", "start", "--workers=4"]

    volumes:
      postgres_data:
    ```
  </Tab>
</Tabs>

### Kubernetes

<Tabs>
  <Tab title="Deployment">
    ```yaml theme={null}
    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
    ```
  </Tab>

  <Tab title="Service">
    ```yaml theme={null}
    apiVersion: v1
    kind: Service
    metadata:
      name: control-plane
    spec:
      selector:
        app: control-plane
      ports:
      - port: 7777
        targetPort: 7777
      type: LoadBalancer
    ```
  </Tab>

  <Tab title="Secrets">
    ```yaml theme={null}
    apiVersion: v1
    kind: Secret
    metadata:
      name: control-plane-secrets
    type: Opaque
    stringData:
      database-url: "postgresql://user:pass@postgres:5432/kubiya"
      secret-key: "your-secret-key-here"
    ```
  </Tab>
</Tabs>

### Nginx Reverse Proxy

```nginx theme={null}
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:

```bash theme={null}
kubiya control-plane start
# [3/4] Running database migrations...
# ✓ Database migrations complete
```

### Backup & Restore

```bash theme={null}
# 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:

```ini theme={null}
[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

```bash theme={null}
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

```bash theme={null}
# 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

<Tabs>
  <Tab title="Local">
    ```bash theme={null}
    tail -f ~/.kubiya/control-plane/logs/server.log
    cat ~/.kubiya/control-plane/logs/server.log | jq '.message'
    ```
  </Tab>

  <Tab title="Docker">
    ```bash theme={null}
    docker-compose logs -f control-plane
    ```
  </Tab>

  <Tab title="Kubernetes">
    ```bash theme={null}
    kubectl logs -f deployment/control-plane
    kubectl logs -l app=control-plane --all-containers=true
    ```
  </Tab>
</Tabs>

## Security

### Production Checklist

<Checklist>
  * [ ] Strong `SECRET_KEY` set via environment variable
  * [ ] Database using SSL/TLS with certificate verification
  * [ ] Redis password protected
  * [ ] Server behind reverse proxy with SSL
  * [ ] Rate limiting configured
  * [ ] Health checks and monitoring in place
  * [ ] Automated backups configured and tested
  * [ ] Log aggregation set up
  * [ ] Secrets in secure secret manager
  * [ ] Database user has least privilege
  * [ ] Network isolation configured
  * [ ] Disaster recovery plan tested
</Checklist>

### Best Practices

<AccordionGroup>
  <Accordion title="Secret Management" icon="key">
    **Never use auto-generated keys in production:**

    ```bash theme={null}
    # 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)
  </Accordion>

  <Accordion title="Database Security" icon="lock">
    **Always use SSL/TLS:**

    ```bash theme={null}
    export DATABASE_URL='postgresql://user:pass@host:5432/db?sslmode=require'
    ```

    **Grant minimal permissions:**

    ```sql theme={null}
    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;
    ```
  </Accordion>

  <Accordion title="Network Security" icon="network-wired">
    **Bind to localhost for local-only access:**

    ```bash theme={null}
    kubiya control-plane start --host=127.0.0.1
    ```

    **Always use reverse proxy for external access with:**

    * SSL/TLS termination
    * Rate limiting
    * WAF protection
  </Accordion>
</AccordionGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Database Connection Failed" icon="database">
    **Error**: `migration failed: connection refused`

    **Solutions**:

    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`
  </Accordion>

  <Accordion title="Port Already in Use" icon="circle-exclamation">
    **Error**: `server did not become healthy`

    **Solutions**:

    1. Find process: `lsof -i:7777`
    2. Kill process: `lsof -ti:7777 | xargs kill`
    3. Use different port: `--port=8888`
  </Accordion>

  <Accordion title="URL Encoding Issues" icon="link">
    **Error**: `could not translate host name`

    **Cause**: Special characters in password not encoded

    **Solution**: Encode special characters:

    * `!` → `%21`, `@` → `%40`, `#` → `%23`, `$` → `%24`, `%` → `%25`

    ```bash theme={null}
    # 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'))"`
  </Accordion>

  <Accordion title="Import Errors" icon="triangle-exclamation">
    **Error**: `ModuleNotFoundError`

    **Solutions**:

    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`
  </Accordion>

  <Accordion title="SECRET_KEY Warning" icon="key">
    **Warning**: `Using auto-generated SECRET_KEY (not suitable for production)`

    **Solution**:

    ```bash theme={null}
    export SECRET_KEY="$(openssl rand -base64 32)"
    kubiya control-plane start
    ```
  </Accordion>
</AccordionGroup>

### Debug Mode

```bash theme={null}
export LOG_LEVEL=DEBUG
kubiya control-plane start
tail -f ~/.kubiya/control-plane/logs/server.log | jq .
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Deploy Workers" icon="server" href="/cli/workers">
    Set up workers to execute agent workflows
  </Card>

  <Card title="Configure LLM Gateway" icon="microchip" href="/cli/custom-llm-gateway">
    Route models through your own LiteLLM proxy
  </Card>

  <Card title="Create Agents" icon="robot" href="/core-concepts/agents">
    Build and configure AI agents
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/overview">
    Explore the full Control Plane API
  </Card>
</CardGroup>
