Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,8 @@ jobs:
build/coder_"$version"_{darwin,windows}_{amd64,arm64}.zip \
build/coder_"$version"_windows_amd64_installer.exe \
build/coder_helm_"$version".tgz \
build/provisioner_helm_"$version".tgz
build/provisioner_helm_"$version".tgz \
build/ai-gateway_helm_"$version".tgz
env:
CODER_SIGN_WINDOWS: "1"
CODER_SIGN_DARWIN: "1"
Expand Down Expand Up @@ -629,14 +630,17 @@ jobs:
mkdir -p build/helm
cp "build/coder_helm_${version}.tgz" build/helm
cp "build/provisioner_helm_${version}.tgz" build/helm
cp "build/ai-gateway_helm_${version}.tgz" build/helm
gsutil cp gs://helm.coder.com/v2/index.yaml build/helm/index.yaml
helm repo index build/helm --url https://helm.coder.com/v2 --merge build/helm/index.yaml
gsutil -h "Cache-Control:no-cache,max-age=0" cp "build/helm/coder_helm_${version}.tgz" gs://helm.coder.com/v2
gsutil -h "Cache-Control:no-cache,max-age=0" cp "build/helm/provisioner_helm_${version}.tgz" gs://helm.coder.com/v2
gsutil -h "Cache-Control:no-cache,max-age=0" cp "build/helm/ai-gateway_helm_${version}.tgz" gs://helm.coder.com/v2
gsutil -h "Cache-Control:no-cache,max-age=0" cp "build/helm/index.yaml" gs://helm.coder.com/v2
gsutil -h "Cache-Control:no-cache,max-age=0" cp "helm/artifacthub-repo.yml" gs://helm.coder.com/v2
helm push "build/coder_helm_${version}.tgz" oci://ghcr.io/coder/chart
helm push "build/provisioner_helm_${version}.tgz" oci://ghcr.io/coder/chart
helm push "build/ai-gateway_helm_${version}.tgz" oci://ghcr.io/coder/chart

- name: Upload artifacts to actions (if dry-run)
if: ${{ inputs.dry_run }}
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/tag-and-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,8 @@ jobs:
build/coder_"$version"_{darwin,windows}_{amd64,arm64}.zip \
build/coder_"$version"_windows_amd64_installer.exe \
build/coder_helm_"$version".tgz \
build/provisioner_helm_"$version".tgz
build/provisioner_helm_"$version".tgz \
build/ai-gateway_helm_"$version".tgz
env:
CODER_SIGN_WINDOWS: "1"
CODER_SIGN_DARWIN: "1"
Expand Down Expand Up @@ -697,14 +698,17 @@ jobs:
mkdir -p build/helm
cp "build/coder_helm_${version}.tgz" build/helm
cp "build/provisioner_helm_${version}.tgz" build/helm
cp "build/ai-gateway_helm_${version}.tgz" build/helm
gsutil cp gs://helm.coder.com/v2/index.yaml build/helm/index.yaml
helm repo index build/helm --url https://helm.coder.com/v2 --merge build/helm/index.yaml
gsutil -h "Cache-Control:no-cache,max-age=0" cp "build/helm/coder_helm_${version}.tgz" gs://helm.coder.com/v2
gsutil -h "Cache-Control:no-cache,max-age=0" cp "build/helm/provisioner_helm_${version}.tgz" gs://helm.coder.com/v2
gsutil -h "Cache-Control:no-cache,max-age=0" cp "build/helm/ai-gateway_helm_${version}.tgz" gs://helm.coder.com/v2
gsutil -h "Cache-Control:no-cache,max-age=0" cp "build/helm/index.yaml" gs://helm.coder.com/v2
gsutil -h "Cache-Control:no-cache,max-age=0" cp "helm/artifacthub-repo.yml" gs://helm.coder.com/v2
helm push "build/coder_helm_${version}.tgz" oci://ghcr.io/coder/chart
helm push "build/provisioner_helm_${version}.tgz" oci://ghcr.io/coder/chart
helm push "build/ai-gateway_helm_${version}.tgz" oci://ghcr.io/coder/chart

- name: Send repository-dispatch event
if: ${{ inputs.release_type != 'rc' && inputs.release_type != 'create-release-branch' }}
Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ push/$(CODER_MAIN_IMAGE): $(CODER_MAIN_IMAGE)
.PHONY: push/$(CODER_MAIN_IMAGE)

# Helm charts that are available
charts = coder provisioner
charts = coder provisioner ai-gateway

# Shortcut for Helm chart package.
$(foreach chart,$(charts),build/$(chart)_helm.tgz): build/%_helm.tgz: build/%_helm_$(VERSION).tgz
Expand Down Expand Up @@ -1060,6 +1060,7 @@ gen/golden-files: \
enterprise/tailnet/testdata/.gen-golden \
helm/coder/tests/testdata/.gen-golden \
helm/provisioner/tests/testdata/.gen-golden \
helm/ai-gateway/tests/testdata/.gen-golden \
provisioner/terraform/testdata/.gen-golden \
tailnet/testdata/.gen-golden
.PHONY: gen/golden-files
Expand Down Expand Up @@ -1396,6 +1397,7 @@ clean/golden-files:
enterprise/tailnet/testdata \
helm/coder/tests/testdata \
helm/provisioner/tests/testdata \
helm/ai-gateway/tests/testdata \
provisioner/terraform/testdata \
tailnet/testdata \
-type f -name '*.golden' -delete
Expand Down Expand Up @@ -1437,6 +1439,10 @@ helm/provisioner/tests/testdata/.gen-golden: $(wildcard helm/provisioner/tests/t
fi
touch "$@"

helm/ai-gateway/tests/testdata/.gen-golden: $(wildcard helm/ai-gateway/tests/testdata/*.yaml) $(wildcard helm/ai-gateway/tests/testdata/*.golden) $(GO_SRC_FILES) $(wildcard helm/ai-gateway/tests/*_test.go)
TZ=UTC go test ./helm/ai-gateway/tests -run=TestUpdateGoldenFiles -update
touch "$@"

coderd/.gen-golden: $(wildcard coderd/testdata/*/*.golden) $(GO_SRC_FILES) $(wildcard coderd/*_test.go)
TZ=UTC go test ./coderd -run="Test.*Golden$$" -update
touch "$@"
Expand Down
3 changes: 3 additions & 0 deletions enterprise/cli/aigatewaystart.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ import (
)

const (
// helm/ai-gateway's terminationGracePeriodSeconds must exceed
// shutdownTimeout so graceful shutdown completes before Kubernetes sends
// SIGKILL.
shutdownTimeout = 5 * time.Minute
traceShutdownTimeout = 5 * time.Second

Expand Down
10 changes: 9 additions & 1 deletion helm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ all: lint
lint: lint/helm
.PHONY: lint

lint/helm: lint/helm/coder lint/helm/provisioner
lint/helm: lint/helm/coder lint/helm/provisioner lint/helm/ai-gateway
.PHONY: lint/helm

lint/helm/coder:
Expand All @@ -25,3 +25,11 @@ lint/helm/provisioner:
helm dependency update --skip-refresh provisioner/
helm lint --strict --set coder.image.tag=v0.0.1 provisioner/
.PHONY: lint/helm/provisioner

lint/helm/ai-gateway:
helm dependency update --skip-refresh ai-gateway/
helm lint --strict \
--set coder.image.tag=v0.0.1 \
--set aigateway.keySecret.name=test \
ai-gateway/
.PHONY: lint/helm/ai-gateway
1 change: 1 addition & 0 deletions helm/ai-gateway/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tests
6 changes: 6 additions & 0 deletions helm/ai-gateway/Chart.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dependencies:
- name: libcoder
repository: file://../libcoder
version: 0.1.0
digest: sha256:5c9a99109258073b590a9f98268490ef387fde24c0c7c7ade9c1a8c7ef5e6e10
generated: "2026-07-14T17:15:50.670279008Z"
24 changes: 24 additions & 0 deletions helm/ai-gateway/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: v2
name: coder-ai-gateway
description: Standalone AI Gateway for Coder
home: https://github.com/coder/coder
# These versions are replaced when Coder releases the chart.
type: application
version: "0.1.0"
appVersion: "0.1.0"
kubeVersion: ">= 1.23.0-0"
keywords:
- coder
- ai
sources:
- https://github.com/coder/coder/tree/main/helm/ai-gateway
icon: https://helm.coder.com/coder_logo_black.png
maintainers:
- name: Coder Technologies, Inc.
email: [email protected]
url: https://coder.com/contact

dependencies:
- name: libcoder
version: 0.1.0
repository: file://../libcoder
189 changes: 189 additions & 0 deletions helm/ai-gateway/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# Coder AI Gateway Helm chart

This chart deploys the Coder AI Gateway as a standalone Kubernetes Deployment.
The Gateway connects to Coder using `CODER_URL` and an AI Gateway key. To forward
proxied AI traffic to the standalone Gateway, configure the Coder AI Gateway
Proxy (`aibridgeproxyd`) after installing the chart.

The chart does not create credentials or TLS Secrets.

## Install

### Prerequisites

- An AI Gateway key created in Coder.
- A Coder image that includes the `coder ai-gateway start` command. The official
Coder v2.36.0 image is the first version to include this command.

### Configure the chart

Create a `values.yaml` file with the Coder URL and an AI Gateway key source. The
following example uses a Kubernetes Secret in the Helm release namespace:

```console
kubectl create secret generic coder-ai-gateway-key \
--namespace <release-namespace> \
--from-literal=key='<AI gateway key>'
```

```yaml
coder:
image:
# Required when installing the chart directly from Git.
tag: "<coder version>"
env:
- name: CODER_URL
value: https://coder.example.com

aigateway:
keySecret:
name: coder-ai-gateway-key
```

The Gateway can also connect to Coder through an in-cluster Service, for
example:

```yaml
coder:
env:
- name: CODER_URL
value: http://coder.coder.svc.cluster.local:80
```

For HTTPS, the Coder certificate must cover the internal Service hostname and
the Gateway must trust its issuing CA.

Instead of `aigateway.keySecret`, set `CODER_AI_GATEWAY_KEY` or
`CODER_AI_GATEWAY_KEY_FILE` through `coder.env`. Environment variables can also
be supplied through `coder.envFrom`. The chart does not check for variable
conflicts, regardless of whether values come from Helm options, `coder.env`, or
`coder.envFrom`.

When installing a released chart package, the chart automatically uses the
matching Coder image version. Set `coder.image.tag` only when installing
directly from Git or overriding the image version. Custom images must provide
the `coder ai-gateway start` command.

### Install the chart

```console
helm install ai-gateway ./helm/ai-gateway \
--namespace <release-namespace> \
--values values.yaml
```

## Connect Coder to the standalone Gateway

To route proxied AI requests through the standalone Gateway, configure the Coder
AI Gateway Proxy with a target URL. When `service.enable` is true, the chart
notes show the direct in-cluster Service URL, including the scheme selected by
`aigateway.listenerTLS`. Retrieve it with:

```console
helm get notes ai-gateway --namespace <release-namespace>
```

The chart notes do not show an Ingress or `HTTPRoute` URL. To route through one
of these entry points, set `CODER_AI_GATEWAY_PROXY_TARGET` to its URL instead.
When `service.enable` is false, set the target to the URL of your user-managed
route to the Deployment.

When listener TLS uses a private CA, the AI Gateway Proxy must trust that CA to
connect directly to the Service over HTTPS.

## TLS

For Gateway-to-Coder HTTPS with a private CA, set
`aigateway.coderTLS.caSecret`. If Coder requires client mTLS, also set
`aigateway.coderTLS.clientSecret`.

Prefer terminating client-facing TLS at a Kubernetes Ingress or a `Gateway`
resource from the Kubernetes Gateway API. To terminate TLS in the AI Gateway
process, set `aigateway.listenerTLS.name` to an existing TLS Secret.

Client-facing TLS and backend TLS are independent. The `ingress.tls` settings
configure TLS between clients and the Ingress. For `HTTPRoute`, the Gateway
listener that accepts client connections is configured outside this chart.
These settings do not configure whether the Ingress or Gateway connects to the
AI Gateway Service using HTTP or HTTPS.

When `aigateway.listenerTLS` is enabled behind an Ingress or `HTTPRoute`,
configure the entry point to connect to the Service using HTTPS and trust the AI
Gateway certificate. Ingress backend TLS is controller-specific and can usually
be configured with `ingress.annotations`. Gateway API backend TLS uses a
separate `BackendTLSPolicy`, which can be managed outside this chart or rendered
with `extraTemplates`. The chart does not infer or validate this
controller-specific configuration. Without backend TLS, the entry point sends
plaintext HTTP to the HTTPS listener, which typically results in a TLS handshake
error reported as HTTP 502.

All referenced TLS Secrets must exist in the Helm release namespace.

## Networking

The data-plane Service, which carries LLM traffic, is a `ClusterIP` by default.
`NodePort` and `LoadBalancer` are explicit alternatives. Ingress and `HTTPRoute`
are optional and both route to the data-plane Service. If you enable Ingress or
`HTTPRoute`, use a `ClusterIP` Service unless you intentionally need a second
external entry point through a `LoadBalancer` Service.

## Scaling and resources

Set `coder.replicaCount` to run multiple AI Gateway replicas. The default
resource requests are 1 CPU and 1 GiB of memory per replica. These requests are
a starting point, not a capacity guarantee. CPU and memory usage depend heavily
on concurrent requests and payload size.

Adjust `coder.resources` after observing production traffic. Consider setting
`CODER_AI_GATEWAY_MAX_CONCURRENCY` through `coder.env` to bound concurrent
requests per replica. The application default is unlimited. The chart does not
set resource limits by default, which avoids CPU throttling and fixed memory
limits for bursty workloads. Manage resources such as a Horizontal Pod
Autoscaler or PodDisruptionBudget through your platform configuration or
`extraTemplates`.

## Metrics

Every pod runs an unauthenticated metrics listener on the named `metrics` port,
which maps to port `2112`. The chart does not create monitoring discovery
resources. Configure pod-based discovery with `coder.podAnnotations`, for
example:

```yaml
coder:
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "2112"
```

Alternatively, create discovery resources such as a `ServiceMonitor` through
your monitoring stack or `extraTemplates`.

## Key rotation

1. Create a new AI Gateway key in Coder.
2. Update the configured key source:
- For `aigateway.keySecret`, update the referenced Secret or set `name` to a
new Secret.
- For a key supplied through `coder.env`, update the environment variable or
the file it references.
3. If the update did not trigger a rollout, restart the Deployment, for example:

```console
kubectl rollout restart deployment/coder-ai-gateway \
--namespace <release-namespace>
```

4. Verify every replica is ready and serving with the new key.
5. Revoke the old key.

Secret updates do not change the Deployment pod template automatically. A
reloader controller can be configured through `coder.annotations` or
`coder.podAnnotations`.

## Extra manifests

`extraTemplates` renders additional Kubernetes manifests as part of the Helm
release. Entries can be YAML strings or Kubernetes objects, and can use Helm
release values and chart helpers. Use them for small companion resources, such
as a `NetworkPolicy`.
13 changes: 13 additions & 0 deletions helm/ai-gateway/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Coder AI Gateway is installed as {{ include "coder.name" . }}.

{{- if .Values.service.enable }}
Configure the Coder deployment with:

CODER_AI_GATEWAY_PROXY_TARGET={{ ternary "https" "http" (not (empty .Values.aigateway.listenerTLS.name)) }}://{{ include "coder.name" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }}
Comment thread
pawbana marked this conversation as resolved.
{{- else }}
The chart-managed Service is disabled. Configure CODER_AI_GATEWAY_PROXY_TARGET
with the URL of the user-managed route to this deployment.
{{- end }}

The metrics listener is active on port 2112. Configure discovery with
coder.podAnnotations or resources managed by your monitoring stack.
Loading
Loading