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:
- Load and validate the identity (service-account) file
- Optionally sync pinned images and the
serve-helmOCI chart to a private registry (--image-sync) - Optionally create a Kubernetes
dockerconfigjsonpull secret (--image-pull-secret-stdin) - Generate Helm values
helm upgrade --installthe Agent Router dataplane chart- 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).
The egress extproc sidecar (ai-gateway-extproc) that the AI Gateway injects
ships with a conservative default CPU request, so the HPA works out of the box.
HPA utilization is computed across every container in the egress pod, and a
container with no CPU request leaves the HPA unable to compute a value
(FailedGetResourceMetric: missing request for cpu in container ai-gateway-extproc).
The request is part of the HPA's aggregate denominator (the target percentage is
measured against the summed envoy + extproc requests), so if the default does
not match the observed traffic, override it in the data plane Helm values
(tare install / tare upgrade --helm-values <file>):
egressResources:
egress:
extProc:
resources:
requests:
cpu: 250m # size to observed extproc CPU (kubectl top pod --containers)
memory: 256Mi
limits:
memory: 512Mi
Set this in the persistent values, not with a live kubectl edit: the egress
objects are reconciled and an in-cluster edit is overwritten. No CPU limit is set
by default (a CPU limit would throttle the sidecar); the memory limit guards OOM.
The shipped values are a conservative starting point, not a universal setting. Under representative load, watch the sidecar with kubectl top pod -n tars-dataplane --containers and adjust for the cluster:
- Raise
limits.memoryif extproc approaches it: the memory limit is a hard cap, so an under-sized value causes OOM kills under heavy or large-payload traffic. - Size
requests.cputo real usage. It feeds the HPA's aggregate denominator, so it directly shifts the scale-out threshold.
Node size, request volume, and payload size all affect the right numbers, so the same values will not fit every environment.
Fresh installs pick up the request automatically. On an upgrade, the running egress pods keep their old request-less sidecar until they restart (the request is injected only at pod admission), so the HPA stays FailedGetResourceMetric until the deployment is rolled:
kubectl rollout restart deployment/egress -n tars-dataplane
When upgrading from a version that predates the GatewayConfig CRD (gatewayconfigs.aigateway.envoyproxy.io), apply CRDs first. tare upgrade does not reapply them, so helm upgrade otherwise fails with no matches for kind "GatewayConfig":
tare install --crds-only # then run your upgrade
--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
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:
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:
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 Helmglobal.imageRegistry) points at the sync destination so the cluster pulls from where the images now live. - Source registry priority:
--image-registry(when--image-syncis also set) →REGISTRY_URLenv →registry.tetrate.ai(default). - The
serve-helmchart 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 rcfinds the newest release candidate.tare installitself still uses the chart embedded in the CLI binary; the mirrored chart exists so downstreamhelm upgrade oci://myregistry.example.com/serve-helmworkflows succeed without reaching back toregistry.tetrate.ai. - For release-tagged
tarebuilds 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 downstreamhelm 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:
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".
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:
# 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 installagainst a namespace they own. - Race-free upgrades: apply CRDs first so subsequent
helm upgraderuns render against the new schema with no chicken-and-egg ordering. - Raw
helm installworkflows: operators who drive Helm directly (GitOps, Argo CD, custom CI) hand the CRD lifecycle totareand let Helm own only the workload resources.
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
To skip tare install for the workload step and run Helm
directly, Helm must be told not to render or adopt the CRDs that tare
already applied via server-side apply. Two flags are needed:
# 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-crdstells Helm to skip CRDs in the chart'scrds/directory.--set ai-gateway-crds.enabled=falsedisables theai-gateway-crdssubchart, which renders CRDs as templates (not ascrds/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:
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-systemandtars-dataplanenamespaces. - Re-runs are idempotent (
kubectl applyunder the hood). - Default secret name:
tars-image-pull-secret. Override with--image-pull-secret-name.
To reference a pre-existing secret without creating one:
tare install identity.json --serve-url https://proxy.acme.com \
--image-pull-secret-name my-existing-secret
Full workflow
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:
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:
# 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:
# 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):
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-stdinboth 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:
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 Console 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
# 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
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:
helm upgrade --install tars charts/valet/serve \
--namespace tars-system --create-namespace \
-f helm/tars-values.yaml
Preview images before syncing
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
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 labeled
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 the chart is installed 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
# 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