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

# Self-Hosted Proxy

> Run the KnoxCall data plane on your own Docker infrastructure. Same binary as the cloud — your payloads never leave your network.

# Self-Hosted Proxy

Self-hosted mode runs the same `knoxcall/proxy` Docker image on your infrastructure. Live API traffic — request bodies, response bodies, injected secrets — stays entirely within your VPC. Only routing metadata, tenant config, and re-encrypted secrets flow from the control plane over TLS.

<Note>
  Requires an **Enterprise** subscription with `byoAgentEnabled` (the same flag gates the Go Agent). Reach out if you need to pilot — we can provision a sandbox agent.
</Note>

## Why Self-Host

* **Data-residency regulation**: HIPAA, FedRAMP-adjacent, EU-residency, sovereign-cloud contracts that require payload data to stay on-prem.
* **Air-gapped environments**: systems without outbound internet except to a single control-plane endpoint.
* **Latency-sensitive workloads**: proxy sits in the same VPC as your apps and upstream APIs.
* **Compliance audits**: every byte of customer data remains under your audit log.

## How It Works

```text theme={"dark"}
┌──────────────────────┐       HTTPS (session bundles,
│  KnoxCall cloud      │       analytics, heartbeat)
│  (control plane)     │ ◀─────────────────────────────┐
│  Admin UI, routes,   │                               │
│  secrets (encrypted) │                               │
└──────────────────────┘                               │
                                                       │
┌──────────────────────────────────────────────────────┤
│  Your infrastructure                                 │
│                                                      │
│  ┌────────────┐    ┌───────────────┐   ┌──────────┐ │
│  │  Your app  │───▶│ knoxcall/     │──▶│ Upstream │ │
│  │            │    │ proxy         │   │   API    │ │
│  │            │    │ (docker)      │   │          │ │
│  └────────────┘    └───────────────┘   └──────────┘ │
│                          │                           │
│                    ┌─────┴─────┐                    │
│                    │ Postgres  │ (your DB)          │
│                    │  Redis    │                    │
│                    └───────────┘                    │
└─────────────────────────────────────────────────────┘
```

* **Control plane** at knoxcall.com holds route config and MASTER\_KEY-wrapped secrets.
* **Your container** pulls a signed [session bundle](#session-bundles) every hour.
* **Request flow**: your app → `knoxcall/proxy` (your infra) → upstream API. Control-plane hop only happens during session-bundle refresh.

## Prerequisites

1. **Credentials**: create a server-mode agent under **Automation → Agents** with `mode: server`. Copy the `kc_agent_xxx` ID and secret (shown only once).
2. **Local Postgres + Redis**: one of each, reachable from the container. SQLite is not supported.
3. **Local MASTER\_KEY\_B64**: generate a 32-byte base64 key locally (`openssl rand -base64 32`). **Do NOT share this with KnoxCall**; it stays on your infrastructure and wraps integration credentials at rest.
4. **Outbound HTTPS** to `knoxcall.com` (for session-bundle refresh + metrics reporting).
5. **Public HTTPS endpoint** (typically `https://proxy.yourdomain.com`) behind your preferred TLS terminator.

## Install

### Docker Compose

Before bringing anything up, generate two strong secrets and place them in a local `.env` file (never commit):

```bash theme={"dark"}
# MASTER_KEY_B64 — stays on your infra; wraps integration credentials at rest
openssl rand -base64 32

# POSTGRES_PASSWORD — database credential; any long random string
openssl rand -base64 24
```

Create `.env` alongside the compose file:

```ini theme={"dark"}
KNOXCALL_TENANT_ID=<from-admin-ui>
KNOXCALL_AGENT_ID=kc_agent_xxx
KNOXCALL_AGENT_SECRET=<shown-once-on-creation>
MASTER_KEY_B64=<openssl output from above>
POSTGRES_PASSWORD=<openssl output from above>
```

Then the compose file:

```yaml theme={"dark"}
services:
  proxy:
    image: knoxcall/proxy:latest
    restart: unless-stopped
    ports: ["8080:8080"]
    environment:
      KNOXCALL_DEPLOYMENT_MODE: self_hosted
      KNOXCALL_TENANT_ID: ${KNOXCALL_TENANT_ID}
      KNOXCALL_AGENT_ID: ${KNOXCALL_AGENT_ID}
      KNOXCALL_AGENT_SECRET: ${KNOXCALL_AGENT_SECRET}
      KNOXCALL_CONTROL_PLANE_URL: https://knoxcall.com
      PG_URL: postgres://kc:${POSTGRES_PASSWORD}@postgres:5432/kc
      MASTER_KEY_B64: ${MASTER_KEY_B64}
      REDIS_URL: redis://redis:6379
    depends_on: [postgres, redis]

  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: kc
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: kc
    volumes: ["./pgdata:/var/lib/postgresql/data"]

  redis:
    image: redis:7-alpine
```

`docker compose up -d`, then point your TLS terminator (Caddy, Nginx, Cloudflare Tunnel, ALB) at `:8080`.

### Health Checks

Once running, the container exposes:

* `GET /healthz` — readiness probe. Returns **503** until the first control-plane sync succeeds, then **200** with sync stats. Returns **503** again if the bundle goes stale (control plane unreachable for >1 hour).
* `GET /livez` — liveness probe. Always **200** while the process is responsive.

Point your orchestrator's readiness probe at `/healthz` so a container with a dead control-plane link is depooled automatically.

## Session Bundles

Every hour the container POSTs to `/agent/v1/session` with its agent credentials and receives a bundle containing:

* **Routes** merged with their production-environment config
* **Secrets** re-encrypted with an HKDF-derived session key (decrypts locally)
* **Tenant metadata** (slug)
* **Environments** and **API keys**

The session key never leaves the container. The MASTER\_KEY\_B64 on the control plane is never sent — secrets are re-encrypted per-window so even a packet capture during refresh yields nothing useful past the current hour.

## Local Integrations

The control plane **never pushes KnoxCall's own integration credentials** (SMTP, Twilio, S3, Anthropic) to a self-hosted container. You configure your own under **Settings → Integrations**. Required minimum for a functional self-hosted deployment:

* **Email provider** — otherwise alerts, invitations, and password resets fail silently.

Optional:

* **S3** — for response-body archival
* **Anthropic** — for AI anomaly detection on your alerts
* **Twilio** — for SMS alerts

## Tamper Seal

Every `knoxcall/proxy` image is built with a two-pass `build_sig`:

1. Compile the image with a placeholder signature.
2. Hash the compiled `dist/` contents.
3. HMAC-SHA256 of `hash:version` with a CI-only secret.
4. Rebuild with the real signature baked in.

The control plane validates this on every session refresh. In strict mode (`KNOXCALL_STRICT_TAMPER_CHECK=true`), an unknown signature = session refused. Default mode logs a tamper event but still issues the session so you have time to investigate.

## Next Steps

* Read the [migration guide](/infrastructure/migration) to cutover from cloud to self-hosted with zero downtime
* Configure [email and other integrations](/infrastructure/self-hosted-proxy#local-integrations) on your self-hosted container
* Run [shadow-mode tests](/infrastructure/migration#shadow-testing) before flipping live traffic
