# `tare gateway install`

> Reference for tare gateway install, which provisions the serve gateway resources that expose the Agent Router data plane.

# `tare gateway install`

  Install serve gateway resources. The provider is selected with <code>--type</code> (<code>gcp</code>, <code>azure</code>, or <code>aws</code>).

## Synopsis

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

## Description

`tare gateway install` deploys the gateway chart used to expose serve traffic.

High-level workflow:

1. Load identity file
2. Load optional `gcp-gateway.json` config (`--config`)
3. Resolve values from flags/config/identity (with provenance: every
   resolved value carries the source it came from)
4. **Plan**: render a diff between the resolved config and live GCP/k8s
   state, with inline lint findings and a customer-action block; in a
   TTY, prompt `Proceed? [y/N]`. CI callers pass `--yes`.
5. Optional prereq workflow (`--dry-run-prereqs` or `--apply-prereqs`)
6. Run preflight checks
7. Install chart with Helm
8. Optional wait for Gateway address (`--wait`) and print DNS A-record action

The Plan view is the same JSON contract (`apiVersion:
tare.tetrate.io/v1alpha1`) used by `tare gateway config lint`; use
`--plan-only --output json` to consume it from other tooling.

### Plan / Confirm / Apply flags

| Flag | Description |
|---|---|
| `--plan-only` | Render the Plan and exit without applying changes. |
| `--yes` | Skip the interactive `Proceed?` prompt. Required in non-TTY contexts (CI). |
| `--accept-warnings` | Proceed even when lint warnings are present. Errors still block. |
| `--output text\|json` | Plan output format. Default: `text`. |

Provider support:

- `gcp`: supported (GCP Cloud Load Balancer + certificate-manager + Helm chart)
- `azure`: supported (AKS + AGIC addon + Ingress): traditional Azure CNI only
- `aws`: supported (EKS + in-tree Network Load Balancer; no AWS Load Balancer Controller, no `aws` CLI)

## Prerequisites

Before install, you must either:

- run `--apply-prereqs`, or
- confirm prerequisites are already done with `--ack-prereqs`

### GCP prerequisites

Expected prerequisites:

- wildcard DNS prepared for your serve hostname
- TLS certificate + certificate map prepared
- static IP / cloud policy resources prepared

If `--apply-prereqs` is used, `gcloud` must be installed and authenticated.

### Azure prerequisites

Expected prerequisites:

- AKS cluster exists with AGIC-compatible networking
  (`networkPlugin: azure`, `networkPluginMode: null`, `networkDataplane: azure`)
- `az` CLI installed and `az login` completed
- signed-in identity has the required Azure roles (see below)
- `kubectl` context points at the target AKS cluster

If `--apply-prereqs` is used, the install runs `az aks enable-addons --addons
ingress-appgw` (which provisions an Application Gateway, ~5min) before
applying the Ingress.

Pre-flights that run before any side effects:

- **AGIC networking compatibility**: `az aks show ... --query networkProfile`
  must report `{plugin: azure, mode: null, dataplane: azure}`. If the cluster
  is Cilium/Overlay, fails fast with a pointer to AGC follow-up.
- **Azure RBAC**: checks `Azure Kubernetes Service Contributor Role` on the
  AKS RG and `Network Contributor` on the `MC_<rg>_<cluster>_<region>` node-RG.
  Missing roles fail with the exact `az role assignment create` command to fix.
  If `Microsoft.Authorization/roleAssignments/read` is itself denied, the
  pre-flight degrades to a warning and proceeds (the install will surface
  AuthorizationFailed itself if a role is genuinely missing).
- **Kubernetes RBAC**: `kubectl auth can-i create ingress -n tars-dataplane`.
  Fails with a hint pointing at the `edit` ClusterRole if denied.

### AWS prerequisites

The AWS path is ingress-only and the lightest of the three. It exposes the data
plane's egress as a Network Load Balancer by setting
`envoyService.type=LoadBalancer` plus the NLB annotation on the egress
**EnvoyProxy** (`tars-egress-proxy` in `tars-system`). The `egress` Service is
Envoy-Gateway-managed, so driving the load balancer through the EnvoyProxy is
durable: Envoy Gateway re-asserts the NLB across pod restarts, Envoy Gateway
restarts, and `tare upgrade`. A direct Service patch reverts to `ClusterIP` on
the next reconcile. The in-tree EKS cloud provider provisions the NLB from the
annotation, so no AWS Load Balancer Controller and no `aws` CLI are required,
and there is no prereq workflow (`--apply-prereqs` and `--ack-prereqs` do not
apply).

Expected prerequisites:

- EKS cluster with the data plane already installed (`tare install`), so the
  egress EnvoyProxy exists in `tars-system`
- `kubectl` context points at the target EKS cluster

Pre-flights that run after the Plan and confirmation, before any mutation:

- **kubectl / cluster reachable**
- **egress EnvoyProxy exists** in the system namespace. Fails with an "is the
  data plane installed?" hint; override the name with `--aws-envoyproxy` if it
  differs.
- **Kubernetes RBAC**: `kubectl auth can-i patch envoyproxies.gateway.envoyproxy.io
  -n tars-system`. Fails with a hint pointing at the needed role if denied.

Identity for credential-less upstream auth (Bedrock, for example) is a separate
step; see [Bedrock workload identity](/agent-router-enterprise/guides/operate-and-govern/provision-models-and-providers/bedrock-workload-identity).
This command only exposes the gateway.

## Config file

Use `--config` to provide gateway settings:

```bash
tare gateway install identity.json --type gcp --config gcp-gateway.json
tare gateway install identity.json --type azure --config azure-gateway.json
```

Full schemas:

- [`gcp-gateway.json` schema](./gcp-gateway-config.mdx)
- [`azure-gateway.json` schema](./azure-gateway-config.mdx)

`--type aws` takes **no config file**. It is configured entirely by flags
(`--aws-internal`, `--aws-service`, `--aws-envoyproxy`).

## Usage

### Render the Plan and exit

```bash
tare gateway install identity.json \
  --type gcp \
  --config gcp-gateway.json \
  --plan-only
```

Outputs the resolved values (with provenance), the GCP/k8s deltas
against live state, inline lint findings, and the customer-action
block. No side effects.

### Print generated Helm values

```bash
tare gateway install identity.json \
  --type gcp \
  --config gcp-gateway.json \
  --print-helm-values
```

### Preview prerequisite `gcloud` commands

```bash
tare gateway install identity.json \
  --type gcp \
  --config gcp-gateway.json \
  --dry-run-prereqs
```

### Apply prerequisites and install

```bash
tare gateway install identity.json \
  --type gcp \
  --config gcp-gateway.json \
  --apply-prereqs \
  --wait
```

### Install when prereqs are already managed externally

```bash
tare gateway install identity.json \
  --type gcp \
  --config gcp-gateway.json \
  --ack-prereqs \
  --wait
```

### Print rendered manifests without applying

```bash
tare gateway install identity.json \
  --type gcp \
  --config gcp-gateway.json \
  --print-resources
```

On the Azure path, `--print-resources` emits the `tars-ingress` YAML (with
the AGIC health-probe annotations) instead of helm-templated chart output.
On the AWS path, it emits the EnvoyProxy merge patch (the
`spec.provider.kubernetes.envoyService` block: `type: LoadBalancer` plus the
NLB annotation).

### Azure: preview the `az` plan without applying

```bash
tare gateway install identity.json \
  --type azure \
  --config azure-gateway.json \
  --dry-run-prereqs
```

### Azure: apply prerequisites and install

```bash
tare gateway install identity.json \
  --type azure \
  --config azure-gateway.json \
  --apply-prereqs --wait
```

Runs the AGIC compat pre-flight, the Azure RBAC pre-flight (printing
remediation commands when needed), and the K8s `can-i create ingress`
pre-flight; then enables the AGIC addon, applies the Ingress with
health-probe annotations, waits for the Ingress to report an address,
and prints the DNS A-record customer action.

### AWS: expose the egress as an NLB

```bash
tare gateway install identity.json \
  --type aws \
  --wait
```

Renders the Plan (the EnvoyProxy patch), confirms (or `--yes` in CI), runs the
kubectl, cluster, and EnvoyProxy pre-flights, patches the egress EnvoyProxy with
`envoyService.type=LoadBalancer` plus
`service.beta.kubernetes.io/aws-load-balancer-type=nlb`, waits for Envoy Gateway
to reconcile the `egress` Service and the in-tree EKS cloud provider to publish
the NLB hostname, then prints the DNS **CNAME** customer action and a smoke-test
`curl` (expect HTTP 401).

### AWS: provision an internal NLB

```bash
tare gateway install identity.json \
  --type aws \
  --aws-internal \
  --wait
```

Adds `service.beta.kubernetes.io/aws-load-balancer-internal=true` so the NLB is
internal-only.

## Flags

### Main

| Flag | Default | Description |
|---|---|---|
| `--type` | `gcp` | Gateway provider type (`gcp\|azure\|aws`) |
| `--config <file>` | none | Path to gateway config (`gcp-gateway.json` or `azure-gateway.json`; not used by `aws`) |
| `--project-id <id>` | from config | (GCP) project for prereq automation |
| `--serve-domain <host>` | from config/serve-url | Hostname/domain used by prereq automation |
| `--certificate-name <name>` | from config | (GCP) certificate resource name for managed cert workflow |
| `--dns-authorization-name <name>` | from config/derived | (GCP) DNS authorization resource name |
| `--serve-url <url-or-host>` | config → identity | Gateway host override |
| `--certificate-map-name <name>` | from config | (GCP) certificate map name |
| `--static-ip-name <name>` | from config | (GCP) optional static IP resource name |
| `--security-policy <name>` | from config | (GCP) optional Cloud Armor security policy name |
| `--customer <name>` | identity/config | Customer label |
| `--environment <name>` | from config | Environment label (required when customer is `saas`) |
| `--ack-prereqs` | `false` | Confirm prereqs are already handled |
| `--dry-run-prereqs` | `false` | Print prereq cloud-CLI plan (`gcloud` or `az`) |
| `--apply-prereqs` | `false` | Run prereq cloud-CLI plan |
| `--wait` | `false` | Wait until Gateway has an address (Azure: Ingress address; AWS: NLB hostname) |

### Azure (only consulted under `--type azure`)

| Flag | Default | Description |
|---|---|---|
| `--azure-subscription-id <uuid>` | from config | Azure subscription scoping `az` calls |
| `--azure-resource-group <name>` | from config | Resource group holding the AKS cluster |
| `--aks-cluster-name <name>` | from config | AKS cluster name |
| `--appgw-name <name>` | `<aks-cluster>-appgw` | Application Gateway name AGIC creates |
| `--appgw-subnet-cidr <cidr>` | `10.225.0.0/24` | App Gateway subnet CIDR |

### AWS (only consulted under `--type aws`)

| Flag | Default | Description |
|---|---|---|
| `--aws-internal` | `false` | Provision an internal NLB (adds the `aws-load-balancer-internal` annotation) |
| `--aws-service <name>` | `egress` | Egress Service Envoy Gateway exposes as an NLB |
| `--aws-envoyproxy <name>` | `tars-egress-proxy` | Egress EnvoyProxy resource to drive the NLB through |

### Inspect

| Flag | Default | Description |
|---|---|---|
| `--print-helm-values` | `false` | Print generated Helm values to stdout |
| `--print-resources` | `false` | Print rendered Kubernetes manifests to stdout |

### Advanced / hidden

| Flag | Default | Description |
|---|---|---|
| `--timeout` | `10m` | Timeout for `--wait` |
| `--release-name` | `tars-gateway` | Helm release name |
| `--namespace` | `tars-gateway` | Helm release namespace |
| `--system-namespace` | `tars-system` | Namespace referenced by gateway routes |
| `--dataplane-namespace` | `tars-dataplane` | Dataplane namespace referenced by gateway routes |
| `--chart-path` | embedded | Override chart source path/OCI/HTTP |
| `--chart-version` | none | Chart version (needed for remote charts) |
| `--helm-values` | none | Extra Helm values file to merge |
| `--skip-preflight` | `false` | Skip preflight checks |

## Output behavior

- Progress and operator guidance are written to `stderr`
- `--print-helm-values` and `--print-resources` write generated artifacts to `stdout`

When `--wait` succeeds, CLI prints:

- resolved Gateway address
- customer DNS A-record action (`Host`, `Type A`, `Value`, `TTL`)
- certificate provisioning reminder

On the AWS path the resolved address is the NLB hostname, and the customer
action is a **CNAME** record rather than an A record, followed by the
smoke-test `curl`.

## Verification

```bash
kubectl get gateway -n tars-gateway
kubectl get httproute -n tars-system
kubectl get httproute -n tars-dataplane
kubectl get gtwpc -n tars-gateway
```

If using managed certificates, also verify certificate state:

```bash
gcloud certificate-manager certificates describe <certificate-name> \
  --project <project-id> \
  --format="yaml(name,managed.state,managed.domainStatus)"
```

On AWS, verify the egress Service has an NLB hostname and smoke-test it:

```bash
kubectl get service egress -n tars-dataplane \
  -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'
curl -sk -o /dev/null -w 'HTTP %{http_code}\n' http://<nlb-hostname>:10080/v1/chat/completions
```

`401` means the ingress is wired through to the `tars-proxy` filter (it is asking
for an API key). `200` means the filter is not loaded; `502` means the load
balancer health check is failing.

## Troubleshooting

- `missing projectId`: set `--project-id` or `projectId` in config when using prereq workflow
- `missing certificate map name`: set `--certificate-map-name` or `certificateMap.name` in config
- wait timeout: check Gateway events and cloud LB provisioning status
- certificate still `PROVISIONING`: ensure DNS authorization record and final A record are correct
- (AWS) `EnvoyProxy "tars-egress-proxy" not found`: run `tare install` first so the egress EnvoyProxy exists in `tars-system`, or pass `--aws-envoyproxy <name>` if it is named differently
- (AWS) the egress Service stays `ClusterIP` after patching: confirm `kubectl get envoyproxy tars-egress-proxy -n tars-system -o jsonpath='{.spec.provider.kubernetes.envoyService.type}'` reads `LoadBalancer`, then check the `envoy-gateway` controller logs; Envoy Gateway must reconcile the EnvoyProxy into the Service
- (AWS) NLB hostname never appears: run `kubectl describe service egress -n tars-dataplane` for events and check subnet discovery tags for the in-tree cloud provider

Where to go next

  <Link to="/reference/tare-cli/gcp-gateway-config" className="tare-nav-card">
    gcp-gateway.json schema
    Full field reference for the GCP gateway config file.
  </Link>
  <Link to="/reference/tare-cli/azure-gateway-config" className="tare-nav-card">
    azure-gateway.json schema
    Full field reference for the Azure gateway config file.
  </Link>
