# Installation & Deployment Modes

> Installing the controller via released YAML, Helm chart, or kind clusters; choosing between core-only and core+extensions deployments.

- Repository: kubernetes-sigs/agent-sandbox
- GitHub: https://github.com/kubernetes-sigs/agent-sandbox
- Human wiki: https://grok-wiki.com/public/wiki/kubernetes-sigs-agent-sandbox-c3f2597a654a
- Complete Markdown: https://grok-wiki.com/public/wiki/kubernetes-sigs-agent-sandbox-c3f2597a654a/llms-full.txt

## Source Files

- `README.md`
- `helm/README.md`
- `k8s/controller.yaml`
- `k8s/extensions.controller.yaml`
- `dev/tools/deploy-to-kube`
- `dev/tools/create-kind-cluster`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [README.md](README.md)
- [helm/README.md](helm/README.md)
- [helm/Chart.yaml](helm/Chart.yaml)
- [helm/values.yaml](helm/values.yaml)
- [helm/templates/deployment.yaml](helm/templates/deployment.yaml)
- [helm/templates/_controller-args.tpl](helm/templates/_controller-args.tpl)
- [helm/templates/clusterrolebinding-extensions.yaml](helm/templates/clusterrolebinding-extensions.yaml)
- [helm/templates/namespace.yaml](helm/templates/namespace.yaml)
- [k8s/controller.yaml](k8s/controller.yaml)
- [k8s/extensions.controller.yaml](k8s/extensions.controller.yaml)
- [k8s/extensions.yaml](k8s/extensions.yaml)
- [k8s/rbac.generated.yaml](k8s/rbac.generated.yaml)
- [k8s/extensions-rbac.generated.yaml](k8s/extensions-rbac.generated.yaml)
- [dev/tools/deploy-to-kube](dev/tools/deploy-to-kube)
- [dev/tools/create-kind-cluster](dev/tools/create-kind-cluster)
- [dev/tools/release](dev/tools/release)
- [Makefile](Makefile)
</details>

# Installation & Deployment Modes

`agent-sandbox` ships as a single Go controller (`agent-sandbox-controller`) plus a small set of CRDs. The same controller binary can run in two reconciliation modes — **core only** (just the `Sandbox` CRD) or **core + extensions** (`Sandbox`, `SandboxClaim`, `SandboxTemplate`, `SandboxWarmPool`) — selected at deploy time by a single `--extensions` flag. This page describes the three supported installation paths, what each path actually applies to the cluster, and how the two deployment modes differ at the manifest level.

The three installation paths correspond to three audiences: cluster operators consuming released artifacts (raw YAML from a GitHub release), platform teams that want templated, upgradeable installs (Helm chart in `helm/`), and contributors hacking on the controller locally (kind cluster scripts under `dev/tools/`). All three converge on the same set of cluster objects: a `Namespace`, a `ServiceAccount`, one or two `ClusterRoleBinding`s, a `Service` for metrics, and a `Deployment` running the controller image.

## What gets installed

Regardless of installation method, a working install contains the objects below. The "Extensions" column indicates which objects only appear when extensions are enabled.

| Object | Kind | Source | Extensions only |
| --- | --- | --- | --- |
| `agent-sandbox-system` | `Namespace` | [k8s/controller.yaml:1-5](k8s/controller.yaml) / [helm/templates/namespace.yaml](helm/templates/namespace.yaml) | No |
| `agent-sandbox-controller` | `ServiceAccount` | [k8s/controller.yaml:9-15](k8s/controller.yaml) | No |
| `agent-sandbox-controller` | `ClusterRole` / `ClusterRoleBinding` | [k8s/rbac.generated.yaml:1-60](k8s/rbac.generated.yaml), [k8s/controller.yaml:19-30](k8s/controller.yaml) | No |
| `agent-sandbox-controller` | `Service` (metrics on `:8080`) | [k8s/controller.yaml:34-48](k8s/controller.yaml) | No |
| `agent-sandbox-controller` | `Deployment` | [k8s/controller.yaml:52-81](k8s/controller.yaml) or [k8s/extensions.controller.yaml:1-32](k8s/extensions.controller.yaml) | No (mode swap) |
| `sandboxes.agents.x-k8s.io` | `CustomResourceDefinition` | `k8s/crds/agents.x-k8s.io_sandboxes.yaml` | No |
| `agent-sandbox-controller-extensions` | `ClusterRole` / `ClusterRoleBinding` | [k8s/extensions-rbac.generated.yaml:1-87](k8s/extensions-rbac.generated.yaml), [k8s/extensions.yaml:1-14](k8s/extensions.yaml) | Yes |
| `sandboxclaims`, `sandboxtemplates`, `sandboxwarmpools` (`extensions.agents.x-k8s.io`) | `CustomResourceDefinition` | `k8s/crds/extensions.agents.x-k8s.io_*.yaml` | Yes |

The controller exposes two container ports: `metrics` on 8080 (also fronted by the `Service`) and `healthz` on 8081, used by the Helm chart's liveness/readiness probes ([helm/templates/deployment.yaml:30-48](helm/templates/deployment.yaml)).

## Deployment topology

```text
                  +------------------------------------------+
                  |  Namespace: agent-sandbox-system         |
                  |                                          |
                  |  ServiceAccount: agent-sandbox-controller|
                  |              |                           |
                  |              v                           |
                  |   Deployment: agent-sandbox-controller   |
                  |   args: --leader-elect=true              |
                  |         [--extensions  if enabled]       |
                  |   ports: metrics:8080  healthz:8081      |
                  |              |                           |
                  |              v                           |
                  |   Service: agent-sandbox-controller      |
                  |             (TCP/8080 metrics)           |
                  +-------------------|----------------------+
                                      |
   Cluster-scoped:                    |
   ClusterRole/ClusterRoleBinding "agent-sandbox-controller"
        (Sandbox CRD + pods/pvcs/services/leases/events)
   ClusterRole/ClusterRoleBinding "agent-sandbox-controller-extensions"
        (only when --extensions is set; adds SandboxClaim/Template/WarmPool
         plus networkpolicies)
   CRDs: agents.x-k8s.io_sandboxes
         extensions.agents.x-k8s.io_{sandboxclaims,sandboxtemplates,sandboxwarmpools}
```

This shape is identical across the three install paths; only the way the manifests are rendered and applied differs.

## Installation path 1 — Released YAML

The released-artifact path is what the README documents for production consumers ([README.md:85-101](README.md)). Two manifests are published on each GitHub release:

| Asset | Contents | Apply when |
| --- | --- | --- |
| `manifest.yaml` | Namespace, `ServiceAccount`, core `ClusterRole`/binding, `Service`, core `Deployment` (without `--extensions`), and the `sandboxes.agents.x-k8s.io` CRD | Always (core install) |
| `extensions.yaml` | Extensions `ClusterRoleBinding`, extensions CRDs (`sandboxclaims`, `sandboxtemplates`, `sandboxwarmpools`) | After `manifest.yaml`, only if you want extensions enabled |

```sh
export VERSION="vX.Y.Z"
# Core only:
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/${VERSION}/manifest.yaml
# Add extensions:
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/${VERSION}/extensions.yaml
```

Both files are produced by `dev/tools/release`. It globs everything under `k8s/`, splits files by basename — anything whose basename starts with `extensions` goes into `extensions.yaml`, everything else into `manifest.yaml` — then rewrites the `ko://` image placeholder with the real release image `registry.k8s.io/agent-sandbox/agent-sandbox-controller:<tag>` ([dev/tools/release:43-85](dev/tools/release)).

> Important: `extensions.yaml` only carries the extra RBAC binding and extension CRDs. It does **not** flip the controller into extensions mode by itself — the released `manifest.yaml` ships the **non-extensions** controller `Deployment` from [k8s/controller.yaml:52-81](k8s/controller.yaml), whose args are just `--leader-elect=true`. To actually run the extensions controllers you must either (a) install via Helm with `controller.extensions=true`, or (b) edit the deployed controller `Deployment` to add `--extensions` to its args (matching [k8s/extensions.controller.yaml:23-25](k8s/extensions.controller.yaml)).

## Installation path 2 — Helm chart

The Helm chart in `helm/` is the recommended path when you want a single, repeatable, value-driven install with first-class support for the mode toggle. Chart metadata: `apiVersion: v2`, `name: agent-sandbox`, `version: 0.1.0` ([helm/Chart.yaml:1-9](helm/Chart.yaml)).

```bash
# Core only
helm install agent-sandbox ./helm/ \
  --namespace agent-sandbox-system \
  --create-namespace \
  --set image.tag=<version>

# Core + extensions (WarmPool, Template, Claim)
helm install agent-sandbox ./helm/ \
  --namespace agent-sandbox-system \
  --create-namespace \
  --set image.tag=<version> \
  --set controller.extensions=true
```

Sourced from [helm/README.md:7-37](helm/README.md). `image.tag` is **required** — `values.yaml` ships an empty string so you must supply it explicitly ([helm/values.yaml:6-9](helm/values.yaml)).

### How the chart wires the mode flag

A single value, `controller.extensions`, drives both the controller flag and the extra RBAC binding:

1. The args template renders `--extensions=true` into the controller container args when the value is true ([helm/templates/_controller-args.tpl:11-13](helm/templates/_controller-args.tpl)).
2. The extra `ClusterRoleBinding` template is wrapped in `{{- if .Values.controller.extensions }}` so it is only produced in extensions mode ([helm/templates/clusterrolebinding-extensions.yaml:1-16](helm/templates/clusterrolebinding-extensions.yaml)).
3. The base `ClusterRole` for extensions, the `Service`, `ServiceAccount`, and CRDs are always present. Helm installs anything in `helm/crds/` automatically before other resources, including the extension CRDs ([helm/README.md:3-5](helm/README.md), [helm/crds/](helm/crds/)).

That last point creates two important operational caveats called out in `helm/README.md`:

- **CRDs are not upgraded** by `helm upgrade`. After bumping the chart version, apply CRDs manually: `kubectl apply -f helm/crds/` ([helm/README.md:48-53](helm/README.md)).
- **CRDs are not deleted** by `helm uninstall`. Removing them with `kubectl delete -f helm/crds/` will cascade-delete every `Sandbox`, `SandboxWarmPool`, `SandboxTemplate`, and `SandboxClaim` in the cluster ([helm/README.md:54-67](helm/README.md)).

### Controller flags exposed through values

The `_controller-args.tpl` partial is the single source of truth for what flags the chart produces. Each value in `.Values.controller.*` maps to one controller flag, and unset values are omitted entirely:

| Helm value | Flag emitted | Purpose |
| --- | --- | --- |
| `controller.leaderElect` | `--leader-elect=` | Enable leader election (default `true`) |
| `controller.leaderElectionNamespace` | `--leader-election-namespace=` | Override lease namespace |
| `controller.clusterDomain` | `--cluster-domain=` | FQDN suffix used by the controller |
| `controller.extensions` | `--extensions=` | Switch to core + extensions mode |
| `controller.enableTracing` | `--enable-tracing=` | OTLP tracing |
| `controller.enablePprof` / `enablePprofDebug` | `--enable-pprof=`, `--enable-pprof-debug=` | Profiling endpoints |
| `controller.pprofBlockProfileRate` / `pprofMutexProfileFraction` | `--pprof-block-profile-rate=`, `--pprof-mutex-profile-fraction=` | Profiling tunables |
| `controller.kubeApiQps` / `kubeApiBurst` | `--kube-api-qps=`, `--kube-api-burst=` | API client throttle |
| `controller.sandboxConcurrentWorkers` | `--sandbox-concurrent-workers=` | Worker pool for `Sandbox` controller |
| `controller.sandboxClaimConcurrentWorkers` | `--sandbox-claim-concurrent-workers=` | Extensions worker pool |
| `controller.sandboxWarmPoolConcurrentWorkers` | `--sandbox-warm-pool-concurrent-workers=` | Extensions worker pool |
| `controller.sandboxTemplateConcurrentWorkers` | `--sandbox-template-concurrent-workers=` | Extensions worker pool |
| `controller.extraArgs` | passthrough | Anything not modeled above (e.g. `--zap-log-level=debug`) |

Defaults and full descriptions are tabulated in [helm/README.md:70-101](helm/README.md). Worker-count flags for extensions resources are only meaningful when `controller.extensions=true`; the controller manager simply will not start those reconcilers otherwise.

### Reusing an existing namespace

`namespace.create` (default `true`) gates the `Namespace` object ([helm/templates/namespace.yaml:1-8](helm/templates/namespace.yaml)). To install into a namespace you manage yourself, set both `namespace.create=false` and `namespace.name=<your-ns>`; the deployment, service account, service, and extensions RoleBinding will all resolve to that namespace through the `agent-sandbox.namespace` helper ([helm/README.md:29-37](helm/README.md), [helm/templates/deployment.yaml:5](helm/templates/deployment.yaml)).

## Installation path 3 — kind for local development

The kind path is wired through the `deploy-kind` Make target and a pair of Python scripts. It is the only path that builds the controller image from your working tree before deploying it.

```bash
# Core only
make deploy-kind

# Core + extensions
EXTENSIONS=true make deploy-kind

# Custom controller flags (do NOT use this to enable extensions)
CONTROLLER_ARGS="--enable-pprof-debug --zap-log-level=debug" make deploy-kind
```

These invocations come straight from [Makefile:31-40](Makefile). The target runs three scripts in sequence: `create-kind-cluster`, `push-images`, and `deploy-to-kube`.

### `create-kind-cluster`

Bootstraps (or recreates) a kind cluster named `agent-sandbox` using an inline `kind.x-k8s.io/v1alpha4` `Cluster` config. The config enables `NodeRestriction` and `OwnerReferencesPermissionEnforcement` admission plugins and turns kubelet verbosity up to `8`; `--containerd-loglevel debug` is the default, and `--kubeconfig bin/KUBECONFIG` exports a kubeconfig for the new cluster ([dev/tools/create-kind-cluster:24-101](dev/tools/create-kind-cluster)). `--recreate` deletes any existing cluster of the same name first ([dev/tools/create-kind-cluster:55-72](dev/tools/create-kind-cluster)).

### `deploy-to-kube`

This is the script that ties the deployment-mode selection to the manifest set. The flow is gather → process → apply ([dev/tools/deploy-to-kube:236-251](dev/tools/deploy-to-kube)):

```text
k8s/**/*.yaml
   |
   v
gather_manifests   --> [ {doc, filename, kind}, ... ]
   |
   v
process_manifests( --extensions ? )
   |   - Skip files whose basename starts with "extensions" unless --extensions
   |   - If --extensions: drop the *core* controller Deployment (replaced by
   |     k8s/extensions.controller.yaml which sets --extensions in args)
   |   - Rewrite ko://... image refs to <image-prefix><cmd>:<tag>
   |   - Append --controller-args flags to the controller container
   |
   v
apply_manifests in three ordered batches via `kubectl apply -f -`:
   1. prereq: Namespace + CRDs
   2. other:  ServiceAccount, ClusterRole(s), ClusterRoleBinding, Service, Deployment
   3. extensions: only if --extensions (extensions ClusterRoleBinding)
```

The mode swap is explicit at [dev/tools/deploy-to-kube:185-194](dev/tools/deploy-to-kube): when `--extensions` is set the core `Deployment` from `k8s/controller.yaml` is skipped because the deployment in `k8s/extensions.controller.yaml` — which adds `--extensions` to the container args ([k8s/extensions.controller.yaml:23-25](k8s/extensions.controller.yaml)) — replaces it.

Image rewriting is path-aware: a value like `ko://sigs.k8s.io/agent-sandbox/cmd/agent-sandbox-controller` is rewritten to `<image_prefix>agent-sandbox-controller:<tag>` ([dev/tools/deploy-to-kube:37-51](dev/tools/deploy-to-kube)). For kind builds the prefix is `kind.local/` and the tag is generated from date + `git describe --always --dirty` when not pinned via `IMAGE_TAG` ([Makefile:39-40](Makefile), [dev/tools/shared/utils.py:33-40](dev/tools/shared/utils.py)).

`--controller-args` (or `CONTROLLER_ARGS` in the Makefile) appends arbitrary flags to the controller container — useful for one-off debugging — but the Makefile help and the script's own help string warn that it must **not** be used to set `--extensions`; use the `EXTENSIONS=true` / `--extensions` toggle so the matching RBAC and Deployment swap also happen ([Makefile:35](Makefile), [dev/tools/deploy-to-kube:268-273](dev/tools/deploy-to-kube), [dev/tools/deploy-to-kube:56-69](dev/tools/deploy-to-kube)).

`make delete-kind` tears the cluster down via `kind delete cluster --name agent-sandbox` ([Makefile:46-48](Makefile)).

## Core vs. core + extensions: choosing a mode

Both modes run the same binary; the difference is what reconcilers it activates and what RBAC and CRDs are required to back them.

| Aspect | Core only | Core + extensions |
| --- | --- | --- |
| Controller arg | `--leader-elect=true` | `--leader-elect=true` plus `--extensions` (or `--extensions=true`) |
| CRDs required | `sandboxes.agents.x-k8s.io` | The above plus `sandboxclaims`, `sandboxtemplates`, `sandboxwarmpools` (all under `extensions.agents.x-k8s.io`) |
| Extra ClusterRoleBinding | none | `agent-sandbox-controller-extensions` (grants `extensions.agents.x-k8s.io/*` plus `networkpolicies`) |
| What the controller reconciles | `Sandbox` | `Sandbox`, `SandboxClaim`, `SandboxTemplate`, `SandboxWarmPool` |
| Released YAML | `manifest.yaml` | `manifest.yaml` + `extensions.yaml` (and you must add `--extensions` to the Deployment args) |
| Helm | default (`controller.extensions=false`) | `--set controller.extensions=true` |
| kind / Make | `make deploy-kind` | `EXTENSIONS=true make deploy-kind` |
| Worker-count flags | `--sandbox-concurrent-workers` | Above plus `--sandbox-claim-…`, `--sandbox-warm-pool-…`, `--sandbox-template-concurrent-workers` |

The extensions-only RBAC additions are visible in [k8s/extensions-rbac.generated.yaml:51-87](k8s/extensions-rbac.generated.yaml): rights over `sandboxclaims`, `sandboxtemplates`, `sandboxwarmpools` (and their `/status`, `/finalizers` subresources) and over `networking.k8s.io/networkpolicies`, which the extension controllers create around warm-pool / claim sandboxes.

A practical rule of thumb: choose **core only** when you just want users (or another controller) to author `Sandbox` objects directly; choose **core + extensions** when you need pooling (`SandboxWarmPool`), templated provisioning (`SandboxTemplate`), or self-service claims (`SandboxClaim`).

## Summary

Three installation paths front the same controller deployment: raw YAML from GitHub releases for production consumers, the `helm/` chart for templated installs with a clean `controller.extensions` toggle, and the `dev/tools/deploy-to-kube` + `create-kind-cluster` pair for local kind development driven from the `Makefile`. Across all three, the mode choice — core only vs. core + extensions — flips a single controller flag (`--extensions`), adds one cluster role binding, and brings the three extension CRDs into play; everything else (namespace, service account, metrics service, core CRD, core RBAC, Deployment shape) is identical.
