# `tare install`

> Reference for tare install, which deploys the complete Agent Router data plane into a Kubernetes cluster in one command.

# `tare install`

  Install the complete Agent Router dataplane in a single command.

## Synopsis

```
tare install <identity-file> [flags]
```

## Description

`tare install` performs the full operator workflow:

1. Load and validate the identity (service-account) file
2. Optionally sync pinned images **and the `serve-helm` OCI chart** to a private registry (`--image-sync`)
3. Optionally create a Kubernetes `dockerconfigjson` pull secret (`--image-pull-secret-stdin`)
4. Generate Helm values
5. `helm upgrade --install` the Agent Router dataplane chart
6. Optionally wait for pods to be ready (`--wait`)

`helm` and `kubectl` are downloaded automatically on first run into
`~/.tare/tools/` and reused on subsequent runs.

By default (`--ha=true`) the data-plane Envoy proxy +
sidecar'd ExtProc deploy with HA-safe values: HPA `minReplicas: 2`,
PDB `minAvailable: 1`, and `EnvoyProxy.spec.shutdown.drainTimeout: 300s`
for graceful drain of in-flight LLM streams. If the target cluster has
no `metrics.k8s.io` APIService (metrics-server absent), `tare install`
prints a non-fatal warning: the install-time floor of 2 still applies
but the HPA cannot autoscale beyond it. Pass `--ha=false` for
single-replica installs (lab / CI).

`--serve-url` is the customer-facing gateway URL for the data plane being
installed. It is **optional**: if omitted, the helm chart leaves
`tars-config[proxy-url]` unrendered and the CLI does not register a URL with
the management plane. Admins can register the URL later from the Admin
Dashboard under **System → Settings → Data planes**, which fires a `PROXY_URL`
configuration event that updates the running data plane's `tars-config`.

When supplied, `tare install` calls the management plane's
`ConfiguratorService.SetDataplaneURL` before running `helm upgrade --install`
so the URL is persisted in `system_settings.dataplane-url:<workspaceId>` for
the data plane's workspace (looked up from the identity-file SA's
`workspaceId`). The chart then seeds `tars-config[proxy-url]` from
`global.serveUrl` at install. No `TriggerUpdate` is fired: the value will
be re-pushed via the standard event flow if an admin later edits it.

## Usage

### Basic install

```bash
tare install identity.json --serve-url https://proxy.acme.com
```

`--serve-url` sets the data plane's customer-facing gateway URL. It flows
into `global.serveUrl` in helm values; the controller-helm chart seeds
`tars-config[proxy-url]` from that value at install. (The separate
`controller.config.proxyUrl` value no longer exists: the controller and
worker pods read the URL from `tars-config[proxy-url]` at runtime, not from
a `PROXY_URL` env var.)

Deploys using the embedded Helm chart with defaults:

| Setting | Default |
|---|---|
| Release name | `tars` |
| System namespace | `tars-system` |
| Dataplane namespace | `tars-dataplane` |
| Customer | inferred from identity file |

### Print Helm values

Print the generated Helm values to stdout without touching the cluster:

```bash
tare install identity.json --serve-url https://proxy.acme.com --print-helm-values
```

Useful for inspecting values or piping into GitOps tooling.
`--dry-run` is accepted as a hidden alias.

### Image sync

Sync all pinned images **and the `serve-helm` OCI chart** from the source
registry to a private registry before installing:

```bash
tare install identity.json --serve-url https://proxy.acme.com \
  --image-sync myregistry.example.com
```

- The SA JWT token is used to authenticate to the source registry.
- After the sync, `--image-registry` (the Helm `global.imageRegistry`) points
  at the sync destination so the cluster pulls from where the images now live.
- Source registry priority: `--image-registry` (when `--image-sync` is also
  set) → `REGISTRY_URL` env → `registry.tetrate.ai` (default).
- The `serve-helm` chart is mirrored as an OCI artifact alongside the images
  (e.g. `myregistry.example.com/serve-helm:0.1.0-rc.4_<sha>`). The tag encodes a
  SemVer build identifier: the `+` build metadata is normalized to `_` because
  OCI tags forbid `+`. The counter lives in the prerelease segment (`rc.4`,
  `alpha.347`) so versions are orderable; `tare versions list --channel rc` finds
  the newest release candidate. `tare install`
  itself still uses the chart embedded
  in the CLI binary; the mirrored chart exists so downstream
  `helm upgrade oci://myregistry.example.com/serve-helm` workflows succeed
  without reaching back to `registry.tetrate.ai`.
- For release-tagged `tare` builds the chart also gets an **extra tag**
  equal to the running CLI version (e.g.
  `myregistry.example.com/serve-helm:v1.2.3`) so a downstream
  `helm install --version <tare-version>` resolves to the chart that
  shipped with that CLI. Dev / dirty builds skip the extra tag to keep
  the mirror free of ambiguous tags.

Sync only (no deploy), `--serve-url` is not required:

```bash
tare install identity.json \
  --image-sync myregistry.example.com --sync-only
```

Mirror-to-mirror sync: pull from an existing private registry and push to a
new one in a single invocation. When paired with `--image-sync`,
`--image-registry` is interpreted as the **source** registry to pull from;
without `--image-sync` it keeps its original meaning of "set the Helm
`global.imageRegistry`".

```bash
tare install identity.json \
  --image-registry acme.registry.example.com \
  --image-sync acme1.registry.example.com \
  --sync-only
```

Print the image list without copying, `--serve-url` is not required:

```bash
# Just the source references
tare install identity.json --print-images

# Source → destination pairs (requires --image-sync)
tare install identity.json \
  --image-sync myregistry.example.com --print-images
```

### Apply CRDs only

Apply the chart's CRDs to the cluster and exit before namespaces, pull
secret, and Helm install. `--serve-url` is not required. Useful for three
operational patterns:

- **Cluster-admin handoff**: a privileged operator pre-applies CRDs (which
  are cluster-scoped), then a less-privileged operator runs the rest of
  `tare install` against a namespace they own.
- **Race-free upgrades**: apply CRDs first so subsequent `helm upgrade`
  runs render against the new schema with no chicken-and-egg ordering.
- **Raw `helm install` workflows**: operators who drive Helm directly
  (GitOps, Argo CD, custom CI) hand the CRD lifecycle to `tare` and let
  Helm own only the workload resources.

```bash
tare install identity.json --crds-only
```

After the CRDs land, re-run without `--crds-only` (and with `--serve-url`)
to complete the install. `--crds-only` cannot be combined with
`--sync-only`, `--image-sync`, `--image-pull-secret-stdin`, or any
`--print-*` flag.

#### Followed by a raw `helm install`

If you intend to skip `tare install` for the workload step and run Helm
directly, you must tell Helm not to render or adopt the CRDs that `tare`
already applied via server-side apply. Two flags are needed:

```bash
# Step 1: apply CRDs with tare (kubectl server-side apply; no Helm ownership labels)
tare install identity.json --crds-only

# Step 2: install the workload with helm, skipping CRDs in both places they live
helm install tars oci://<registry>/serve-helm \
  --version "<version>" \
  -f values.yaml -n tars-system \
  --skip-crds \
  --set ai-gateway-crds.enabled=false
```

- `--skip-crds` tells Helm to skip CRDs in the chart's `crds/` directory.
- `--set ai-gateway-crds.enabled=false` disables the `ai-gateway-crds`
  subchart, which renders CRDs as templates (not as `crds/` files) and
  would otherwise fail Helm's ownership-adoption check against the
  kubectl-applied CRDs.

Without both flags, Helm errors with:

```
CustomResourceDefinition "aigatewayroutes.aigateway.envoyproxy.io" ...
exists and cannot be imported into the current release: invalid ownership
metadata; label validation error: missing key "app.kubernetes.io/managed-by"
```

This split (`tare` owns CRDs, Helm owns the workload) is the
recommended pattern for GitOps and raw-Helm consumers. CRDs survive
`helm uninstall tars` because Helm does not manage them, so customer data
(Gateways, AIGatewayRoutes, MCPRoutes, …) is preserved.

### Pull secret

Create a Kubernetes `dockerconfigjson` secret from credentials read on stdin:

```bash
echo "user:password" | tare install identity.json \
  --serve-url https://proxy.acme.com \
  --image-sync myregistry.example.com \
  --image-pull-secret-stdin
```

- The secret is created (or updated) in both `tars-system` and
  `tars-dataplane` namespaces.
- Re-runs are idempotent (`kubectl apply` under the hood).
- Default secret name: `tars-image-pull-secret`.  Override with
  `--image-pull-secret-name`.

To reference a pre-existing secret without creating one:

```bash
tare install identity.json --serve-url https://proxy.acme.com \
  --image-pull-secret-name my-existing-secret
```

### Full workflow

```bash
echo "user:password" | tare install identity.json \
  --serve-url https://proxy.acme.com \
  --image-sync myregistry.example.com \
  --image-pull-secret-stdin \
  --wait
```

### With guardrails

Include `llm-guard-api` and `envoy-dm` images and enable guardrails
configuration:

```bash
echo "user:password" | tare install identity.json \
  --serve-url https://proxy.acme.com \
  --image-sync myregistry.example.com \
  --image-pull-secret-stdin \
  --enable-guardrails \
  --wait
```

The guardrails container (`llm-guard-api`) requires a node with sufficient
memory and CPU.  Use `--guardrails-config` to pin it to the right node pool:

```bash
# guardrails.yaml
# nodeSelector:
#   node-type: gpu-large
#
# tolerations:
#   - key: large-node
#     operator: Exists
#     effect: NoSchedule

echo "user:password" | tare install identity.json \
  --serve-url https://proxy.acme.com \
  --image-sync myregistry.example.com \
  --image-pull-secret-stdin \
  --enable-guardrails \
  --guardrails-config guardrails.yaml \
  --wait
```

The config file uses natural Kubernetes YAML, no escaped JSON strings:

```yaml
# guardrails.yaml
nodeSelector:
  node-type: gpu-large

tolerations:
  - key: large-node
    operator: Exists
    effect: NoSchedule

# Optional: full affinity rules
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
        - matchExpressions:
            - key: cloud.google.com/gke-nodepool
              operator: In
              values: [llm-guard-pool]
```

Pass `--guardrails-config -` to read the config from stdin instead of a file
(follows the `kubectl`/`helm` convention):

```bash
cat <<'EOF' | tare install identity.json \
  --serve-url https://proxy.acme.com \
  --enable-guardrails \
  --guardrails-config - \
  --wait
nodeSelector:
  node-type: gpu-large
tolerations:
  - key: large-node
    operator: Exists
    effect: NoSchedule
EOF
```

> **Note:** `--guardrails-config -` and `--image-pull-secret-stdin` both read
> stdin; use a file when combining both.

### Print Kubernetes resources (GitOps)

Print namespace and pull-secret manifests without touching the cluster.
`--serve-url` is not required:

```bash
echo "user:password" | tare install identity.json \
  --image-sync myregistry.example.com \
  --image-pull-secret-stdin \
  --print-resources
```

Pipe directly into `kubectl apply` or commit to a GitOps repo.

## Flags

### Main

| Flag | Default | Description |
|---|---|---|
| `--serve-url <URL>` | optional | Data plane gateway URL; sets `global.serveUrl` (which seeds `tars-config[proxy-url]`) and registers the URL with the management plane. Omit to defer URL registration to the Admin Dashboard under **System → Settings → Data planes**. |
| `--ha` | `true` | HA-safe defaults for the data-plane Envoy proxy: HPA `minReplicas: 2`, PDB `minAvailable: 1`. Pass `--ha=false` for single-replica lab/CI installs. |
| `--drain-timeout-seconds` | `300` | `EnvoyProxy.spec.shutdown.drainTimeout` (seconds). Maximum time Envoy waits for in-flight LLM streams to finish before SIGKILL. The Envoy Gateway controller propagates this into the data-plane Pod's `terminationGracePeriodSeconds`. |
| `--enable-guardrails` | `false` | Include guardrails images and configuration |
| `--guardrails-config <file\|->`  | none | YAML file with Guardrails pod scheduling constraints (nodeSelector, affinity, tolerations), or `-` to read from stdin; requires `--enable-guardrails` |
| `--wait` | `false` | Wait for pods ready after deploy |

### Registry

| Flag | Default | Description |
|---|---|---|
| `--image-sync <REG>` | none | Sync images and the `serve-helm` OCI chart to `<REG>` before install; sets `--image-registry` to `<REG>` after the sync (unless `--image-registry` was used to override the source) |
| `--image-pull-secret-stdin` | `false` | Read `user:password` from stdin and create a pull secret |
| `--image-pull-secret-name <NAME>` | `tars-image-pull-secret` | Name of the pull secret to create or reference |
| `--sync-only` | `false` | Sync images then exit without deploying (requires `--image-sync`) |

### Inspect

| Flag | Default | Description |
|---|---|---|
| `--print-helm-values` | `false` | Print generated Helm values to stdout without deploying |
| `--print-resources` | `false` | Print Kubernetes manifests (namespaces, pull secret) to stdout without applying |
| `--print-images` | `false` | Print image list and exit (add `--image-sync` for src→dst pairs) |
| `--crds-only` | `false` | Apply CRDs to the cluster and exit; skip namespaces, pull secret, Helm install, and wait. Incompatible with `--sync-only`, `--image-sync`, `--image-pull-secret-stdin`, and any `--print-*` flag. |

### Advanced / hidden

These flags are hidden from `--help` but accepted for advanced use.

| Flag | Default | Description |
|---|---|---|
| `--dry-run` | `false` | Alias for `--print-helm-values` |
| `--release-name` | `tars` | Helm release name |
| `--system-namespace` | `tars-system` | Kubernetes system namespace |
| `--dataplane-namespace` | `tars-dataplane` | Kubernetes dataplane namespace |
| `--chart-path` | embedded | Path or OCI/HTTP reference to Helm chart |
| `--chart-version` | none | Chart version (required for remote/OCI charts) |
| `--image-registry` | none | Container image registry. Without `--image-sync`: sets Helm `global.imageRegistry`. With `--image-sync`: source registry to pull from (push destination is `--image-sync`). Falls back to `REGISTRY_URL` env, then `registry.tetrate.ai` |
| `--image-tag` | none | Image tag override |
| `--helm-values` | none | Additional Helm values file to merge |
| `--deployment-mode` | `enterprise` | `enterprise` or `saas` |
| `--environment` | none | Environment label |
| `--customer` | from identity | Customer label override |
| `--redis-type` | `cluster` | `cluster` or `hosted` |
| `--rate-limiting` | `true` | Enable rate limiting |
| `--timeout` | `5m` | Timeout for `--wait` |
| `--guardrails-node-selector` | none | Node selector for Guardrails pods as `key=value` pairs (repeatable); prefer `--guardrails-config` |
| `--guardrails-affinity` | none | Affinity rules for Guardrails pods as a JSON object; prefer `--guardrails-config` |
| `--guardrails-tolerations` | none | Tolerations for Guardrails pods as a JSON array; prefer `--guardrails-config` |

## Examples

### Production deployment with private registry

```bash
# 1. Sync images (run once per release upgrade)
tare install identity.json \
  --image-sync gcr.io/acme/tars \
  --sync-only

# 2. Install (or upgrade)
echo "user:password" | tare install identity.json \
  --serve-url https://proxy.acme.com \
  --image-sync gcr.io/acme/tars \
  --image-pull-secret-stdin \
  --release-name acme-serve \
  --wait
```

### GitOps: generate values only

```bash
tare install identity.json \
  --serve-url https://proxy.acme.com \
  --image-registry gcr.io/acme/tars \
  --image-pull-secret-name acme-pull-secret \
  --guardrails-config guardrails.yaml \
  --enable-guardrails \
  --print-helm-values > helm/tars-values.yaml
```

Commit `helm/tars-values.yaml` and apply separately:

```bash
helm upgrade --install tars charts/valet/serve \
  --namespace tars-system --create-namespace \
  -f helm/tars-values.yaml
```

### Preview images before syncing

```bash
tare install identity.json \
  --image-sync myregistry.example.com \
  --enable-guardrails \
  --print-images
```

Output:
```
registry.tetrate.ai/gateway:v1.6.5 → myregistry.example.com/gateway:v1.6.5
registry.tetrate.ai/ratelimit:v1.6.5 → myregistry.example.com/ratelimit:v1.6.5
...
registry.tetrate.ai/llm-guard-api:v0.3.1 → myregistry.example.com/llm-guard-api:v0.3.1
registry.tetrate.ai/envoy-dm:v0.3.1 → myregistry.example.com/envoy-dm:v0.3.1
registry.tetrate.ai/serve-helm:0.1.0-rc.4_abcd123 → myregistry.example.com/serve-helm:0.1.0-rc.4_abcd123
```

## Output

All progress is written to **stderr**; only `--print-helm-values`, `--print-images`,
and `--print-resources` write to stdout, making them safe to redirect:

```
Syncing images registry.tetrate.ai → myregistry.example.com
Copying registry.tetrate.ai/gateway:v1.6.5 → myregistry.example.com/gateway:v1.6.5
2009/11/10 23:00:00 existing blob: sha256:abc123...
...
Image sync complete.

Creating pull secret "tars-image-pull-secret" in namespace tars-system...
  pull secret "tars-image-pull-secret" ok
Creating pull secret "tars-image-pull-secret" in namespace tars-dataplane...
  pull secret "tars-image-pull-secret" ok

Installing Agent Router dataplane...
  release:             tars
  system namespace:    tars-system
  dataplane namespace: tars-dataplane

Creating Kubernetes namespaces...
  namespace tars-system: ok
  namespace tars-dataplane: ok

Deploying with Helm...
  Helm deployment complete

Installation complete.

Next steps:
  kubectl get pods -n tars-system
  kubectl logs -n tars-system -l app=tars
```

## Verification

```bash
kubectl get pods -n tars-system
kubectl get pods -n tars-dataplane
kubectl logs -n tars-system -l app=tars
```

## Management-plane event reporting

After the install completes, the CLI writes a ConfigMap labelled
`tars.io/component=install-event` to `tars-system`. The
`tare-doctor` CronJob (deployed with this chart) forwards that ConfigMap to
the management plane on its next tick (default: every 5 minutes) so the
workspace's deployment history reflects the new install. On a successful
forward the ConfigMap is deleted; on a 5xx / network failure it is retained
for the next tick; on a 4xx it is dropped (the same payload would re-reject).

If you install the chart without going through the `tare` CLI (raw `helm
install`, ArgoCD / Flux, customer GitOps), the same dashboard row appears via
the `tare-doctor` reconciler which observes the helm release secret and emits an event with `source:
tare-doctor-poll`. Operator attribution is empty on the non-CLI path
(rendered as a dash in the timeline).

## Rollback and uninstall

```bash
# View history
helm history tars -n tars-system

# Rollback
helm rollback tars -n tars-system

# Uninstall
helm uninstall tars -n tars-system
kubectl delete namespace tars-system tars-dataplane
```

Where to go next

  <Link to="/reference/tare-cli/upgrade" className="tare-nav-card">
    tare upgrade
    Move an installed dataplane to a newer version with zero downtime.
  </Link>
  <Link to="/reference/tare-cli/uninstall" className="tare-nav-card">
    tare uninstall
    Tear down the dataplane and remove its namespaces and CRDs.
  </Link>
