Skip to main content

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.


warning

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 tare CLI syncs Agent Router release artifacts into the private registry.
  • tare applies CRDs (Custom Resource Definitions) outside Helm.
  • tare renders Helm values.
  • helm installs 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-system and tars-dataplane namespaces.
  • The in-cluster egress service 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

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:

ToolInstall
az CLIhttps://learn.microsoft.com/cli/azure/install-azure-cli
kubectlhttps://kubernetes.io/docs/tasks/tools
helm (3+)https://helm.sh/docs/intro/install
dockerhttps://docs.docker.com/get-docker
curlPreinstalled on macOS and most Linux distributions
tare CLICovered 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.

warning

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.

SizeUse caseRecommended node poolApproximate allocatable target
SmallDev / test / low traffic3 × Standard_B2s≥ 6 vCPU, ≥ 20 GiB RAM
MediumStaging / light production3 × Standard_D4s_v5≥ 12 vCPU, ≥ 40 GiB RAM
HighProduction with burst headroom3 × 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.

note

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:

  1. Click Register Data plane.
  2. Set Data plane ID to a stable identifier such as acme-prod.
  3. Optionally set Name, Description, and Labels.
  4. Leave URL blank for now unless the final gateway hostname is already known.
  5. Click Register Data plane.

Then generate the credential from the data plane row:

  1. Click the row's edit action to open the Data plane drawer.
  2. In the Credential section, click Generate credential.
  3. Save the downloaded file as data-plane-credentials.json on 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-system and tars-dataplane when required
  • the pull secret is referenced from values.yaml; if not, re-render values with --image-pull-secret-name
  • values.yaml points global.imageRegistry at 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.