# Kubernetes Install

> Install the Apoxy controller into a cluster, verify the install, and debug common failure modes.

This guide walks through installing the Apoxy controller into a Kubernetes cluster. Use this
path when your source of truth for Gateway API resources lives in Kubernetes.

## Install the controller

`apoxy k8s install` fetches the onboarding manifests from the Apoxy API and applies them to
your target cluster:

```bash title="terminal"
apoxy k8s install \
  --context <kube-context> \
  --namespace apoxy \
  --cluster-name <cluster-name> \
  --mirror gateway \
  --yes
```

### Available flags

<dl>
  <dt>--kubeconfig</dt><dd>Explicit kubeconfig path.</dd>
  <dt>--dry-run</dt><dd>Print the manifests without applying them.</dd>
  <dt>--force</dt><dd>Force overwrite conflicting fields during apply.</dd>
  <dt>--image</dt><dd>Override the controller image embedded in the onboarding manifests.</dd>
  <dt>--yes</dt><dd>Skip interactive confirmation.</dd>
</dl>

## Your first install

Authenticate and install the controller:

```bash title="terminal"
apoxy auth login
apoxy k8s install \
  --context prod-us-west-2 \
  --namespace apoxy \
  --cluster-name prod-us-west-2 \
  --mirror gateway
```

During installation, the CLI resolves your kubeconfig and context, requests onboarding
manifests from the Apoxy API, and applies resources into the target namespace. On subsequent
runs, if you omit `--cluster-name`, the CLI recovers it from the namespace annotation
`apoxy.dev/cluster-name`.

## Mirror mode

The `--mirror` flag controls which resource types the controller mirrors to Apoxy:

<DocsTable>
| Mode     | What it mirrors                                          | When to use                                                              |
|----------|-----------------------------------------------------------|---------------------------------------------------------------------------|
| gateway  | Gateway API resources (`gateway.networking.k8s.io`)       | Gateway API is your primary routing configuration.                       |
| ingress  | Ingress resources                                         | You're bridging an existing Ingress workflow into Apoxy.                 |
| all      | Both Gateway API and Ingress                              | Mixed environments running both resource families.                       |
</DocsTable>

## Preview with dry-run

Before applying to a cluster, preview the manifests:

```bash title="terminal"
apoxy k8s install \
  --context dev-cluster \
  --namespace apoxy \
  --cluster-name dev-cluster \
  --mirror gateway \
  --dry-run
```

## Verify the install

After installing, confirm these basics:

- The kubeconfig path resolves to the cluster you intended.
- The selected context matches the target cluster.
- The namespace is correct.
- The Apoxy CLI is authenticated (`apoxy auth login`).
- The project selection in your CLI config matches the target environment.

## Common errors

<dl>
  <dt><code>failed to load kubeconfig</code></dt><dd>Bad kubeconfig path or incorrect current context.</dd>
  <dt><code>failed to get YAML</code></dt><dd>Authentication issue or API-side onboarding problem.</dd>
  <dt>Apply conflicts</dt><dd>Use <code>--force</code> only when you understand the field ownership implications.</dd>
  <dt>Non-interactive runs without <code>--yes</code></dt><dd>The CLI requires confirmation by default.</dd>
</dl>

## How the controller authenticates to Apoxy

The first time the controller starts in your cluster, it issues itself a per-cluster client
certificate from the Apoxy control plane and uses it as its identity for every subsequent
request. There is no long-lived API key in the cluster after install completes.

The flow:

1. `apoxy k8s install` writes the onboarding manifests — including a one-time bootstrap secret
   keyed to the cluster name — into your cluster.
2. The controller pod starts, exchanges the bootstrap material for a client certificate via the
   Apoxy API, and stores it in Secret `apoxy/apiz-cert`:
   - `tls.crt` — the certificate, signed by Apoxy.
   - `tls.key` — the matching private key (generated in the pod; never leaves the cluster).
   - `ca.crt` — Apoxy's root, used to verify the upstream.
3. The controller and the aggregated API server (`v1alpha.core.apoxy.dev` and siblings) use
   this cert as a client certificate when proxying to Apoxy. The control plane authenticates
   each request by certificate fingerprint and authorizes against the project the cert was
   issued for.

The cert's subject encodes the service user `kube-controller-<cluster-name>`, so a cert minted
for one cluster cannot impersonate another even within the same project.

### Cert lifetime

Issued certificates are valid for 365 days. The controller **auto-renews** them when validity
drops below 30 days: an hourly tick checks the live cert, and on the first tick below threshold
the controller re-issues against Apoxy over mTLS with its current cert and hot-reloads the new
material in place (no pod restart). See [Auto-rotation](/docs/guides/rotating-kube-controller-cert.md#auto-rotation)
for the full lifecycle and how to disable it.

You can also rotate explicitly with `apoxy k8s certs rotate` (see [Rotating the kube-controller
certificate](/docs/guides/rotating-kube-controller-cert.md)). Manual rotation has two modes:

- **Restart mode** (default) — the CLI patches the pod template to force a rolling restart;
  the new pod loads the new cert at startup.
- **Hot-reload mode** (`--no-restart`) — the controller watches its projected `apiz-cert`
  Secret mount via fsnotify and atomically swaps the upstream client cert without a pod
  restart. Same mechanism the auto-renewer uses.

The Apoxy API publishes the cert's fingerprint and expiry; the CLI can inspect both:

```bash title="terminal"
apoxy k8s certs list --context <kube-context>
```

### Revocation

Revocation is project-scoped and immediate. When you revoke a fingerprint via `apoxy k8s certs
revoke <fp> --user-jwt <jwt>`, the Apoxy ext_authz layer drops the cert from its
allowed-fingerprint set within ~30 seconds, after which any request presenting that cert fails
with `403`. Revoke requires a user JWT (your dashboard session token), not an API key — a
leaked API key sitting next to the cert in the cluster cannot revoke the cert it lives next
to.

## Updating the install

Re-run `apoxy k8s install` when you need to:

- Change the mirror mode.
- Switch to a custom controller image.
- Pick up updated onboarding manifests from Apoxy.

Use `--cluster-name` explicitly in automation so the intended cluster identity is always
clear.
