Installation guide for Azure using Helm
This guide installs the Agent Router data plane on Azure Kubernetes Service (AKS) using Helm and a private container registry.
This guide assumes platform teams own the Helm release lifecycle and operate a private container registry (Azure Container Registry, Harbor, JFrog Artifactory, Nexus, or Google Artifact Registry). If not, refer to the Installation guide for Azure.
Architecture
Agent Router Enterprise uses a split-plane model. A management plane hosted by Tetrate holds configuration, license data, and the web dashboard. A data plane runs in a customer-managed Kubernetes cluster and handles all AI traffic. The two planes communicate over a single outbound HTTPS connection initiated by the data plane. No inbound internet connections are required.
The data plane requires a stable public hostname so applications can reach it; TLS certificates are issued for hostnames, not IP addresses. Gateway exposure, DNS, and TLS are out of scope for this guide.
The workflow is:
- The
tareCLI syncs Agent Router release artifacts into the private registry. tareapplies CRDs (Custom Resource Definitions) outside Helm.tarerenders Helm values.helminstalls or upgrades the data plane workload from the private registry.
This guide covers data plane installation only. It does not create a public Gateway, ingress, DNS record, or TLS certificate. For gateway exposure, follow the published cloud install guide for the target platform, or use the organization's preferred ingress or Gateway stack.
Outcomes
By the end of this guide:
- Agent Router release artifacts are mirrored into a private container registry.
- Agent Router CRDs (Custom Resource Definitions) are applied to the target Kubernetes cluster.
- The data plane is deployed via Helm into the
tars-systemandtars-dataplanenamespaces. - The in-cluster
egressservice is verified and reachable via port-forward for smoke tests.
Plan for 30--60 minutes, plus separate time for the platform team to provision the gateway, DNS, and TLS.
In this guide
- Prepare for the installation: obtain the data plane credential and install the CLI
- Registry setup - mirror Agent Router artifacts into the private registry
- Data Plane installation - apply CRDs and deploy with Helm
- Verify - confirm the installation works end-to-end
- Operations - manage and troubleshoot the installation
Prerequisites
Dashboard and Router app access
Agent Router exposes two web surfaces. Both URLs are provided during onboarding:
- Dashboard (admin):
https://dashboard.<your-tenant>.tetrate.ai. - Router app (end-user):
https://router.<your-tenant>.tetrate.ai.
Required tools
Install the following on the workstation used for this guide:
| Tool | Install |
|---|---|
az CLI | https://learn.microsoft.com/cli/azure/install-azure-cli |
kubectl | https://kubernetes.io/docs/tasks/tools |
helm (3+) | https://helm.sh/docs/intro/install |
docker | https://docs.docker.com/get-docker |
curl | Preinstalled on macOS and most Linux distributions |
tare CLI | Covered in Step 2 |
A data-plane-credentials.json file is also required. See Step 1.
Infrastructure
A dedicated workload cluster must be provisioned before starting the installation. The cluster requires at least three (3) nodes. See Cluster sizing for more detail.
Tetrate support does not cover client-side infrastructure provisioning or Kubernetes issues. The instructions for creating clusters and related infrastructure components are provided as a courtesy and should be carefully evaluated before executing them.
Furtermore, make sure you have sufficient systems/infrastructure permissions to:
- Access the target Kubernetes cluster.
- Push images to the private registry mirror.
- Pull images from the target cluster to the same private registry.
Cluster sizing
The default chart installs multiple always-on components: egress envoy (minimum 2 replicas), AI gateway controller and ext_proc, controller and worker, Redis, and rate-limit services. A single-node footprint is not sufficient.
The egress envoy is the dominant resource consumer. Its CPU and memory usage scale with the configuration size held in memory: the count of AIGatewayRoute and AIServiceBackend resources, header-mutation rules, and per-route features. The AI gateway team's control-plane scaling benchmark shows roughly linear CPU and memory growth as routes are added. Plan capacity for route counts that grow as providers, models, and projects are added. General-purpose VM sizes from the Standard_D*s_v5 family provide a balanced default.
| Size | Use case | Recommended node pool | Approximate allocatable target |
|---|---|---|---|
| Small | Dev / test / low traffic | 3 × Standard_B2s | ≥ 6 vCPU, ≥ 20 GiB RAM |
| Medium | Staging / light production | 3 × Standard_D4s_v5 | ≥ 12 vCPU, ≥ 40 GiB RAM |
| High | Production with burst headroom | 3 × Standard_D8s_v5 (or split into system + data plane pools) | ≥ 24 vCPU, ≥ 80 GiB RAM |
Maintain a minimum of three nodes to tolerate node upgrades and evictions. Demo installs may start at Small; production installations should start at Medium.
AKS Automatic is not supported.
Conventions
All commands assume the environment variables defined in Step 3 are exported in the current shell. Re-export them when opening a new terminal.
Replace every <placeholder> value with a site-specific one.
Step 1: obtain the data plane credential
In the dashboard, go to Settings > Data planes.
If the data plane is not registered yet:
- Click Register Data plane.
- Set Data plane ID to a stable identifier such as
acme-prod. - Optionally set Name, Description, and Labels.
- Leave URL blank for now unless the final gateway hostname is already known.
- Click Register Data plane.
Then generate the credential from the data plane row:
- Click the row's edit action to open the Data plane drawer.
- In the Credential section, click Generate credential.
- Save the downloaded file as
data-plane-credentials.jsonon the machine where the install will run.
Each data plane uses its own credential. Credentials can be rotated or revoked from the same Credential section in the data plane edit drawer.
Step 2: install the tare CLI
Run the installer script:
curl -sSL https://tare.tetrate.ai/tools/install.sh | bash
Output:
==> tare installer
==> channel: stable
==> Detected platform: darwin-arm64
==> Installing tare for darwin-arm64...
==> Downloading from: https://tare.tetrate.ai/tools/tags/v0.1.0-beta.4/tare-darwin-arm64.tar.gz
ok Installed tare to /Users/johndoe/.tare/bin/tare
==> tare version: tare version v0.1.0-beta.4
ok Installation directory is already in your PATH
==> Get started:
tare install identity.json --serve-url https://proxy.acme.com
tare install --help
The installer prints the install path (typically ~/.tare/bin/tare). Add it to PATH and verify the version:
export PATH="$PATH:$HOME/.tare/bin"
echo 'export PATH="$PATH:$HOME/.tare/bin"' >> ~/.zshrc # or ~/.bashrc
$ tare --version
tare version v0.1.0-beta.4
Step 3: set variables
For Azure Container Registry:
DP_CREDENTIAL=./data-plane-credentials.json # Obtained in Step 1
PRIVATE_REGISTRY_HOST=${ACR_NAME}.azurecr.io
PRIVATE_IMAGE_REGISTRY=${ACR_NAME}.azurecr.io/tare
PULL_SECRET=tare-registry-pull # this value will be used later on, leave it as-is
For another private registry:
DP_CREDENTIAL=./data-plane-credentials.json # Obtained in Step 1
PRIVATE_REGISTRY_HOST=registry.acme.example.com # Replace with your own registry host
PRIVATE_IMAGE_REGISTRY=registry.acme.example.com/tare # Replace with your own registry full URL
PULL_SECRET=tare-registry-pull # this value will be used later on, leave it as-is
REGISTRY_USERNAME=<registry-user>
REGISTRY_PASSWORD=<registry-password-or-token>
Step 4: sync release artifacts
Log in locally with credentials that can push to the destination registry.
For ACR:
az acr login --name "${ACR_NAME}"
For a generic private registry:
printf '%s' "${REGISTRY_PASSWORD}" | \
docker login "${PRIVATE_REGISTRY_HOST}" \
--username "${REGISTRY_USERNAME}" \
--password-stdin
Copy the pinned Agent Router images and the serve-helm OCI chart:
tare install "${DP_CREDENTIAL}" \
--image-sync "${PRIVATE_IMAGE_REGISTRY}" \
--sync-only \
--parallel 2 \
--stall-threshold 5m
tare uses the data plane credential to pull from Tetrate's source registry. The local registry login is used only to push to the private registry.
Step 5: apply CRDs
Apply CRDs outside Helm:
tare install "${DP_CREDENTIAL}" \
--crds-only \
--image-registry "${PRIVATE_IMAGE_REGISTRY}"
CRDs are managed outside the Helm release so that helm uninstall does not delete cluster-scoped APIs and cascade-delete custom resources.
Step 6: render Helm values
Render values for the Helm release:
tare install "${DP_CREDENTIAL}" \
--image-registry "${PRIVATE_IMAGE_REGISTRY}" \
--print-helm-values > values.yaml
Treat values.yaml as sensitive: it contains the data plane identity secret. Do not commit it in plaintext. Use SOPS, Sealed Secrets, External Secrets, or the organization's approved secret workflow.
If the public data plane URL is not known yet, confirm the rendered values do not set it to the management-plane API URL. The following fields must remain empty:
global:
serveUrl: ""
controller:
config:
proxyUrl: ""
Override them during Helm install if needed:
--set-string global.serveUrl= \
--set-string controller.config.proxyUrl=
Step 7: create namespaces and pull secrets
Create the data plane namespaces:
kubectl create namespace tars-system --dry-run=client -o yaml | kubectl apply -f -
kubectl create namespace tars-dataplane --dry-run=client -o yaml | kubectl apply -f -
For AKS attached to ACR, no image pull secret is usually required after:
az aks update \
--resource-group "${RESOURCE_GROUP}" \
--name "${AKS_CLUSTER_NAME}" \
--attach-acr "${ACR_NAME}"
For a private registry that requires Kubernetes pull credentials, create the same secret in both namespaces:
kubectl create secret docker-registry "${PULL_SECRET}" \
--docker-server="${PRIVATE_REGISTRY_HOST}" \
--docker-username="${REGISTRY_USERNAME}" \
--docker-password="${REGISTRY_PASSWORD}" \
--namespace tars-system \
--dry-run=client -o yaml | kubectl apply -f -
kubectl create secret docker-registry "${PULL_SECRET}" \
--docker-server="${PRIVATE_REGISTRY_HOST}" \
--docker-username="${REGISTRY_USERNAME}" \
--docker-password="${REGISTRY_PASSWORD}" \
--namespace tars-dataplane \
--dry-run=client -o yaml | kubectl apply -f -
When using a pull secret, render values with:
tare install "${DP_CREDENTIAL}" \
--image-registry "${PRIVATE_IMAGE_REGISTRY}" \
--image-pull-secret-name "${PULL_SECRET}" \
--print-helm-values > values.yaml
Step 8: install or upgrade with Helm
Set the chart version for the target Agent Router release:
CHART_VERSION="0.1.0-alpha.1+17f076b"
Install or upgrade the release:
helm upgrade --install tars "oci://${PRIVATE_IMAGE_REGISTRY}/serve-helm" \
--version "${CHART_VERSION}" \
-f values.yaml \
-n tars-system \
--skip-crds \
--set ai-gateway-crds.enabled=false \
--set-string global.serveUrl= \
--set-string controller.config.proxyUrl=
Use --skip-crds and --set ai-gateway-crds.enabled=false together. The first skips chart crds/ entries; the second disables AI Gateway CRDs rendered as normal Helm templates.
Helm expects the SemVer build-metadata form with +, such as 0.1.0-alpha.1+17f076b. OCI stores the tag internally with _, but helm --version must use the + form.
Step 9: verify
Check the Helm release:
helm list -n tars-system
helm status tars -n tars-system
Wait for workloads:
kubectl rollout status deployment/controller -n tars-system --timeout=300s
kubectl rollout status deployment/egress -n tars-dataplane --timeout=300s
Confirm images come from the private registry:
kubectl get deploy egress -n tars-dataplane \
-o jsonpath='{range .spec.template.spec.containers[*]}{.name}={.image}{"\n"}{end}'
Run doctor:
tare doctor "${DP_CREDENTIAL}" --verbose
A fresh data plane with no routes can report warnings for missing RouteDeployments, AIGatewayRoutes, HTTPRoutes, or EnvoyPatchPolicies. The core install is healthy when pods roll out and images pull from the private registry.
Step 10: expose the data plane
This Helm workflow installs the data plane but does not create a public Gateway, ingress, DNS record, or TLS certificate.
For gateway exposure, follow the published cloud install guide for the target platform, or use the organization's preferred ingress or Gateway stack.
The data plane serves traffic through the in-cluster egress service:
- Service:
egress - Namespace:
tars-dataplane - Port:
10080 - Paths:
/v1/*,/mcp/*, and/.well-known/*
Once the gateway and DNS are ready, set the data plane URL in the dashboard: Settings > Data planes > edit row > URL.
Step 11: run validation and smoke tests
Validate the Helm install first:
helm status tars -n tars-system
kubectl rollout status deployment/controller -n tars-system --timeout=300s
kubectl rollout status deployment/egress -n tars-dataplane --timeout=300s
kubectl get deploy egress -n tars-dataplane \
-o jsonpath='{range .spec.template.spec.containers[*]}{.name}={.image}{"\n"}{end}'
tare doctor "${DP_CREDENTIAL}" --verbose
Run traffic smoke tests after onboarding has configured provider keys and routes. This Helm workflow does not create a public Gateway or DNS record. Until the gateway is exposed, use a local port-forward to the in-cluster egress service:
kubectl port-forward -n tars-dataplane svc/egress 18080:10080
In another terminal, set the local endpoint and an API key from the router app:
export DP_SCHEME=http
export DP_HOST=127.0.0.1:18080
export TARS_API_KEY=<api-key-from-router-app>
At minimum, verify model listing with and without authentication:
curl -s -o /tmp/tare-models.json -w "%{http_code}\n" \
"${DP_SCHEME}://${DP_HOST}/v1/models" \
-H "Authorization: Bearer ${TARS_API_KEY}"
curl -s -o /tmp/tare-models-no-auth.json -w "%{http_code}\n" \
"${DP_SCHEME}://${DP_HOST}/v1/models"
Expected result: the authenticated request returns 200; the unauthenticated request returns 401.
Also smoke test every API shape enabled during onboarding, such as /v1/chat/completions, /v1/responses, /v1/messages, /v1/embeddings, /v1/images/generations, and MCP profile paths.
Upgrade and rollback
Use the same split workflow for upgrades: update CRDs first, then upgrade the Helm workload release.
If the new Agent Router release includes CRD changes, re-run CRD apply:
tare install "${DP_CREDENTIAL}" \
--crds-only \
--image-registry "${PRIVATE_IMAGE_REGISTRY}"
Render values for the new release:
tare install "${DP_CREDENTIAL}" \
--image-registry "${PRIVATE_IMAGE_REGISTRY}" \
--print-helm-values > values.yaml
When using a pull secret, include it when rendering values:
tare install "${DP_CREDENTIAL}" \
--image-registry "${PRIVATE_IMAGE_REGISTRY}" \
--image-pull-secret-name "${PULL_SECRET}" \
--print-helm-values > values.yaml
Upgrade the workload release:
CHART_VERSION="<new-chart-version>"
helm upgrade tars "oci://${PRIVATE_IMAGE_REGISTRY}/serve-helm" \
--version "${CHART_VERSION}" \
-f values.yaml \
-n tars-system \
--skip-crds \
--set ai-gateway-crds.enabled=false \
--set-string global.serveUrl= \
--set-string controller.config.proxyUrl=
Rollback uses Helm's normal rollback flow:
helm rollback tars -n tars-system
CRDs are not affected by rollback because they are not part of the Helm release manifest. Existing custom resources remain in the cluster.
If artifacts are already mirrored
If the platform team has already mirrored the Agent Router images and chart, skip the --image-sync step and start at CRD apply:
tare install "${DP_CREDENTIAL}" \
--crds-only \
--image-registry "${PRIVATE_IMAGE_REGISTRY}"
Then render values and install with Helm as described above.
Troubleshooting
Destination registry reports repository not found
Create the destination repository before syncing. For example, with Google Artifact Registry:
gcloud artifacts repositories create tare \
--repository-format docker \
--location <region>
ACR and many enterprise registries create repositories on first push, but some registries require pre-creation.
Helm reports invalid ownership metadata
This usually means Helm is trying to adopt CRDs that were already applied by tare --crds-only.
Example:
CustomResourceDefinition "aigatewayroutes.aigateway.envoyproxy.io" exists and cannot be imported into the current release: invalid ownership metadata
Install or upgrade with both flags:
--skip-crds \
--set ai-gateway-crds.enabled=false
Helm reports improper constraint
helm --version expects a SemVer constraint, not a bare commit SHA. Use the chart's full SemVer, such as 0.1.0-alpha.1+17f076b, or a release-build Agent Router version such as v0.1.0-beta.3.
Bare commit SHAs are rejected before Helm contacts the registry. The registry stores SemVer build metadata with _, but helm --version must use the SemVer form with +.
Pods are stuck in imagepullbackoff
Check that:
- the private registry contains every image tag for the Agent Router release
- the cluster identity can pull from the private registry
- pull secrets exist in both
tars-systemandtars-dataplanewhen required - the pull secret is referenced from
values.yaml; if not, re-render values with--image-pull-secret-name values.yamlpointsglobal.imageRegistryat the intended registry
Helm cannot pull the chart from the mirror
If helm install or helm upgrade fails locally with pull access denied, log in to the OCI registry from the operator machine:
helm registry login "${PRIVATE_REGISTRY_HOST}"
This is separate from Kubernetes image pull access. Helm pulls the chart artifact from the local machine; the cluster pulls workload images using node identity or image pull secrets.
Where to go next