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

# Migrating Between Cloud and Self-Hosted

> Cutover workflow for moving a tenant from KnoxCall Cloud to self-hosted Docker (and back), with shadow-mode testing and readiness gates.

# Migrating Between Cloud and Self-Hosted

<Note>
  This page covers switching a KnoxCall **tenant between Cloud and self-hosted deployment modes**. If you're looking to migrate secrets from AWS Secrets Manager, Azure Key Vault, or GCP Secret Manager into KnoxCall, see the [Secret Store Migrations](/essentials/migrations/overview) section instead.
</Note>

Switching a tenant between deployment modes is an in-place operation on `tenants.deployment_mode`. Cloud → self-hosted is gated by a readiness checklist; the reverse direction is always permitted.

## Switch Flow

1. **Set up the self-hosted proxy** on your infrastructure per the [self-hosted guide](/infrastructure/self-hosted-proxy). Container must be reachable over HTTPS.
2. **Configure the self-hosted proxy URL** under **Settings → Deployment**.
3. **Run the readiness checks** — all four must go green:
   * Proxy reachable (HTTPS GET `/healthz` returns 200)
   * Server-agent heartbeat healthy (under 2 minutes since last heartbeat)
   * Shadow tests (24h) — at least one successful, zero mismatches
   * Email provider configured (alerts, invitations, password resets)
4. **Confirm switch** — the button is disabled until the checklist passes or you explicitly force it.
5. **DNS cutover** — flip your client DNS from `tenant.knoxcall.com` to `proxy.yourdomain.com`. A 7-day cloud fallback is left warm in case you need to revert.

## Shadow Testing

Shadow mode lets you compare the cloud and self-hosted proxies side-by-side *before* cutover:

* **Settings → Deployment → Shadow test** fires a synthetic probe at both data planes and diffs the responses.
* Each run is recorded in `migration_diffs` (30-day retention).
* The readiness dashboard aggregates the last 24 hours of matches vs divergences.

Run several shadow tests covering the routes that matter most — auth routes, high-volume endpoints, routes with large request bodies — before flipping.

<Note>
  The first-cut shadow test pings `/healthz` on both sides. Route-level body comparison (sending the same authenticated request through both proxies and byte-diffing the response) is planned for a future release.
</Note>

## Readiness Gates

The "Switch to Self-hosted" button is enabled only when all of these hold:

| Check                  | Threshold                                                             |
| ---------------------- | --------------------------------------------------------------------- |
| Self-hosted proxy URL  | Set, valid `http://` or `https://`                                    |
| Proxy `/healthz`       | Returns **200** within 5s                                             |
| Server-agent heartbeat | under 2 min since last heartbeat (healthy)                            |
| Shadow test history    | ≥1 test run, 0 mismatches in the last 24 hours                        |
| Email integration      | An `email` row exists in `integration_configs` and a test send worked |

If you need to override (you've validated through another mechanism), the API accepts `{ force: true }` — the UI surfaces this as a "Force switch" confirm button when readiness is red.

## During Migration

Once you've confirmed the switch:

* `tenants.deployment_mode` flips to `self_hosted`.
* The cloud proxy starts returning `404` for any request addressed to this tenant.
* Your self-hosted proxy begins answering traffic.
* DNS change propagates (TTL-dependent — usually minutes).
* The cloud tenant record remains so control-plane operations (managing routes, viewing analytics) continue to work.

## Rolling Back

The reverse direction is one click:

1. **Settings → Deployment → Switch to KnoxCall Cloud**
2. DNS cutover from `proxy.yourdomain.com` back to `tenant.knoxcall.com`

There's no readiness gate on the reverse switch — the cloud proxy is always available for active tenants.

## Rate Limits

Shadow tests are rate-limited to **10 per minute per tenant** to protect the cloud proxy and your self-hosted container from admin-triggered load. Exceeding it returns `429` with a `Retry-After` header.

## Troubleshooting

* **Readiness check stays red despite reachable proxy**: confirm the public URL is what the control plane sees. A NAT or load-balancer rewriting paths will break `/healthz`.
* **`session_denied: tamper_detected`** in container logs: your `knoxcall/proxy` image's `build_sig` isn't in `agent_versions`. Pull `knoxcall/proxy:latest` and restart.
* **Alerts stopped after cutover**: check **Settings → Integrations → Email**. The self-hosted container doesn't inherit the cloud's email provider — you must configure your own.
* **"Bundle stale" 503 responses**: the container can't reach knoxcall.com. Check firewall / egress rules; the only outbound endpoint needed is `knoxcall.com:443`.
