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

# Kubernetes

> What a generic Kubernetes cluster must provide before the setup script runs

**Any conformant CNCF Kubernetes, v1.29 or newer — the reference environment.** The setup script installs pinned Argo CD and Kyverno, your contract and the channel public key, applies the subscription, and runs the [preflight](/preflight); from then on the cluster updates itself, and re-running the [preflight](/preflight) on a schedule turns a drifting cluster visibly RED instead of letting it fail quietly on a later sync.

| Requirement                                                                                        | Why                                                                                                                                                                              |
| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Kubernetes ≥ 1.29, API reachable from the setup machine                                            | the one-time bootstrap                                                                                                                                                           |
| A default StorageClass                                                                             | Postgres, MinIO, Redis volumes                                                                                                                                                   |
| LoadBalancer Services **or** an ingress you manage                                                 | reaching the gateway and dashboard                                                                                                                                               |
| 2 nodes × 4 vCPU / 8 GB (or equivalent)                                                            | runs the full stack; measured, not estimated                                                                                                                                     |
| Headroom **per concurrent bot**: it requests 500m / 1Gi and its limit ships at 2 vCPU / **2560Mi** | the only measurement we hold is production p50 **922Mi**, max **1292Mi**, across 10 bots — the limit carries that plus the 2Gi memory-backed `/dev/shm`, which counts against it |
| Outbound reachability to the channel registry (or your own mirror)                                 | the subscription pulls; nothing pushes in                                                                                                                                        |

## Status

**This is the path our own production runs on, and the only one exercised end-to-end: Linode LKE.** `lke` is the single profile carrying `PROFILE_TESTED=yes`. What has been run, and what has not, is tabulated in [what's proven, and where](/tested); the 2026-08-25 adoption of Vexa's own production is written up in [Upgrade](/upgrade). `--provider generic` differs from `lke` only in that flag and the notes beside it: no conformant cluster other than LKE has run this end-to-end, so on anything else expect deltas and let the preflight find them. Per-provider deltas: [GKE](/environments/gke) · [EKS](/environments/eks) · [AKS](/environments/aks) · [OpenShift](/environments/openshift).

## No ingress controller and no cert-manager

Nothing in the delivery machinery needs them — the subscription pulls, so it needs no inbound path at all. They matter only for the endpoints **you** expose. The pattern that works in that cluster class is a **per-service reverse proxy with built-in ACME** — a small Caddy Deployment in the namespace with its own LoadBalancer Service and certificate storage: one workload per exposed endpoint, a load balancer each, blast-radius isolation.

Two things to get right:

* **Pin the ACME issuer, to production.** Caddy's default fallback chain ends at Let's Encrypt **staging**, so a DNS record that had not propagated is enough to get a staging certificate issued silently. Set `acme_ca` to `https://acme-v02.api.letsencrypt.org/directory` with no fallback. Create the DNS record **before** the proxy pod first starts — ACME fires on boot. Recovery is to delete the stored certificates and restart the rollout.
* **Confirm the issuer from outside, not from the logs.** A staging certificate is invisible from inside the cluster: the pod is `Running`, TLS handshakes complete — and every client sees `x509: certificate signed by unknown authority`.

  ```bash theme={null}
  openssl s_client -connect <host>:443 -servername <host> </dev/null 2>/dev/null \
    | openssl x509 -noout -issuer -dates
  ```

Everything installed is stock upstream plus configuration you can read in one sitting; nothing of ours runs privileged. Air-gapped variant: mirror the channel into your own registry — digests and signatures survive mirroring, and verification pins our key, not a hostname.

Next: [GKE](/environments/gke) · [EKS](/environments/eks) · [AKS](/environments/aks) · [OpenShift](/environments/openshift) · [Install](/install)
