Skip to main content

Getting Started

Self-Hosted Kubernetes

Install AIControls into an existing Kubernetes cluster using the published Helm chart. No source code required.

Prerequisites

  • Kubernetes ≥ 1.27
  • Helm ≥ 3.12
  • kubectl with cluster-admin access (for the initial install)
  • A persistent volume provisioner (any StorageClass that supports ReadWriteOnce)

Step 1 — Get the Helm chart

The AIControls chart is published as an OCI artifact. No helm repo add needed — reference it directly with oci://:

helm show chart oci://ghcr.io/nirmata/charts/aicontrols
VERSION=1.9.0
helm show chart oci://ghcr.io/nirmata/charts/aicontrols --version $VERSION

Step 2 — Prepare your values file

Create a values.yaml. Uncomment and fill in the sections that apply to your cluster.

What belongs in values.yaml vs. the Admin UI

values.yaml only covers deployment settings — things like storage, networking, and TLS. Rate limits, budgets, LLM upstreams, OIDC, and other app-level settings are configured after install, from Settings in the Admin UI, and only ever read values.yaml once, on first boot. See Configuration Model for the full split.

# ── Admin account ─────────────────────────────────────────────────────────────
# Required — the email address for the bootstrap admin account. This becomes the
# first admin user's login identity and is used to issue Personal Access Tokens
# during onboarding. It cannot be changed after the account is created, and the
# install fails with a clear error if left unset.
admin:
email: "" # REQUIRED — set to your email address; install fails until this is set

# ── Storage ─────────────────────────────────────────────────────────────────
storage:
persistentVolume:
enabled: true
size: 10Gi
storageClass: "" # leave empty to use the cluster default

# ── External access ──────────────────────────────────────────────────────────
# Gateway API — single ingress point with path-based routing (recommended).
# Requires Gateway API CRDs in the cluster (e.g. Envoy Gateway, Istio, Cilium).
# Install Envoy Gateway: helm upgrade --install eg oci://docker.io/envoyproxy/gateway-helm \
# --version v1.3.0 -n envoy-gateway-system --create-namespace --wait
gateway:
enabled: true
className: eg # Envoy Gateway default. Use "istio", "cilium", "nginx", etc.
hosts:
- aicontrols.example.com
# tls:
# - secretName: aicontrols-tls
# hosts: [aicontrols.example.com]

# Classic Ingress — alternative for clusters without Gateway API CRDs.
# ingress:
# enabled: true
# className: nginx
# hosts:
# - host: aicontrols.example.com
# paths:
# - { path: /mcp/admin, pathType: Prefix, port: admin }
# - { path: /mcp, pathType: Prefix, port: mcp }
# - { path: /sse, pathType: Prefix, port: mcp }
# - { path: /auth, pathType: Prefix, port: mcp }
# - { path: /v1, pathType: Prefix, port: mcp }
# - { path: /api/v1, pathType: Prefix, port: admin }
# - { path: /, pathType: Prefix, port: admin }
# tls:
# - secretName: aicontrols-tls
# hosts: [aicontrols.example.com]
LLM upstream and policies are configured in the UI

After first login, the setup wizard walks you through connecting an LLM upstream (Anthropic, OpenAI, Bedrock, Ollama, etc.) and creating your first policies. You do not need to put API keys in values.yaml.

Don't have Gateway API installed yet?

Install Envoy Gateway — a CNCF project that implements the Gateway API spec:

helm upgrade --install eg \
oci://docker.io/envoyproxy/gateway-helm \
--version v1.3.0 \
-n envoy-gateway-system \
--create-namespace \
--wait

This installs the Gateway API CRDs and the controller. The chart does not create a GatewayClass — create one yourself, pointing at the Envoy Gateway controller:

kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: eg
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
EOF

If your cluster already has Istio, Cilium, or another Gateway API provider, set gateway.className to match and skip this step.

If you don't want to install a gateway provider right now, set gateway.enabled: false and use the classic Ingress or port-forward options in Step 6.


Step 3 — Install via Helm

helm install aicontrols \
oci://ghcr.io/nirmata/charts/aicontrols \
-n aicontrols \
--create-namespace \
-f values.yaml

Helm prints next steps when the install completes. To upgrade later:

helm upgrade aicontrols \
oci://ghcr.io/nirmata/charts/aicontrols \
--version NEW_VERSION \
-n aicontrols \
-f values.yaml

Step 4 — Retrieve the bootstrap admin password

The Helm chart generates a random 20-character password on first install and stores it in a Kubernetes Secret. Retrieve it now.

kubectl get secret aicontrols-admin-bootstrap \
-n aicontrols \
-o jsonpath='{.data.password}' | base64 -d
echo # print newline after the password
First login

After you log in for the first time, the UI will prompt you to set a permanent password. The bootstrap Secret is kept after helm uninstall (via helm.sh/resource-policy: keep) so it is not accidentally deleted if you reinstall. Delete it manually once you have set a permanent password.

Setting a known password

To set a specific password instead of a random one, add admin.password: YOUR_PASSWORD to values.yaml before installing. This is useful for dev clusters where you want a stable, known password.

admin.email is required

helm install/helm upgrade fails with admin.email is required unless admin.email is set in values.yaml (Step 2). This applies to upgrades of existing installs too — set it before upgrading a release created with an older chart version.


Step 5 — Verify the deployment

# Wait for the pod to become ready:
kubectl rollout status deployment/aicontrols -n aicontrols

# Check pod and service:
kubectl get pods,svc -n aicontrols
# NAME READY STATUS RESTARTS AGE
# pod/aicontrols-7d9f84c5b9-xk2pq 1/1 Running 0 45s
#
# NAME TYPE CLUSTER-IP PORT(S)
# svc/aicontrols ClusterIP 10.96.142.31 8080/TCP,8081/TCP,9081/TCP,9082/TCP

# Tail logs:
kubectl logs -n aicontrols deploy/aicontrols --follow

The proxy is ready when you see started proxy in the logs. The health endpoints are:

EndpointPort
GET /healthz8081 (admin)
GET /readyz8081 (admin)
GET /metrics8081 (admin)
GET/HEAD /8080 (proxy), 8081 (admin)

GET/HEAD / returns 200 OK on both ports specifically for cloud load balancers and Gateways whose default health check probes / and expects a 200 (GKE, EKS, and others do this unless you attach a custom health-check policy). This lets those default checks pass with no extra configuration. On port 8081, when the admin UI is enabled, a browser request to / (one sending Accept: text/html) is redirected to /ui/ instead — health-check probes, which don't send that header, still get a plain 200. For explicit liveness and readiness wiring, prefer /healthz and /readyz.


Step 6 — Expose the service

Forward directly to the aicontrols Service — no gateway needed:

kubectl port-forward -n aicontrols svc/aicontrols 8080:8080 8081:8081
  • Admin UI: http://localhost:8081/ui/
  • MCP proxy: http://localhost:8080

Step 7 — TLS (optional)

Skip this step if you are using port-forward for local access — TLS is not needed there.

The chart can generate a self-signed certificate and store it in a Secret. The cert is created once on first install and reused on every helm upgrade (idempotent via lookup). Valid for 10 years.

warning

Self-signed certificates are not trusted by browsers or most clients without importing the CA. Use only for evaluation or air-gapped clusters. For production, use cert-manager or provide your own certificate.

Enable in values.yaml and upgrade:

tls:
selfSigned:
enabled: true

gateway:
enabled: true
hosts:
- aicontrols.example.com # used as the certificate CN / SAN

The chart creates a Secret named <release>-tls and automatically adds an HTTPS listener (port 443) to the Gateway.


Step 8 — Log in and complete setup

Open the admin UI in a browser. Use the URL that matches how you exposed the service in Step 6:

Exposure methodAdmin UI URL
Port-forward (direct)http://localhost:8081/ui/
Gateway API port-forwardhttp://localhost:8080/ui/
Ingress / hostnamehttps://aicontrols.example.com/ui/

Log in with username admin and the bootstrap password retrieved in Step 4. You will be prompted to set a permanent password on first login.

After changing your password, the setup wizard launches automatically. It walks you through:

  1. LLM provider — connect Anthropic, OpenAI, AWS Bedrock, Azure OpenAI, GCP Vertex AI, or a self-hosted model (Ollama, vLLM). Your API key is stored as a Kubernetes Secret and never exposed in the UI.
  2. MCP servers — add upstream MCP servers that AIControls will proxy and govern.
  3. First policy — create a starter governance policy (allowlist, HITL trigger, or budget).

Complete the wizard before connecting agent pods — agents need at least one upstream configured to route traffic through.


Step 9 — Activate your license

AIControls needs a signed license before it will process any AI traffic (MCP tool calls or LLM requests) — the Admin UI stays browsable, but requests are blocked with 402 until a license is loaded. helm install/helm upgrade also prints this reminder in the post-install notes if proxy.licenseFile isn't set.

On-prem licenses are bound to this cluster's kube-system namespace UID, not to a Nirmata account, so a license issued for one cluster won't activate on another. Retrieve the UID:

kubectl get namespace kube-system -o jsonpath='{.metadata.uid}'

Email that UID to support@nirmata.com to request a license. Once you receive the signed .lic file, see License management to provision it.


Step 10 — Connect agent pods

Agent pods must send MCP and LLM traffic through AIControls instead of calling upstream servers directly.

Set MCP_SERVER_URL (and optionally OPENAI_BASE_URL for the LLM proxy) in your agent pod spec:

apiVersion: v1
kind: Pod
metadata:
name: my-claude-agent
namespace: agents
spec:
serviceAccountName: my-agent-sa
containers:
- name: agent
image: my-org/my-ai-agent:latest
env:
- name: MCP_SERVER_URL
value: "http://aicontrols.aicontrols.svc.cluster.local:8080"
# For OpenAI-compatible clients (Claude Code, Cursor, etc.):
- name: OPENAI_BASE_URL
value: "http://aicontrols.aicontrols.svc.cluster.local:8080/v1"

For OIDC identity validation, each agent pod must also present a projected ServiceAccount token:

spec:
serviceAccountName: my-agent-sa
containers:
- name: agent
env:
- name: MCP_AUTH_TOKEN_FILE
value: /var/run/secrets/aicontrols/token
volumeMounts:
- name: proxy-token
mountPath: /var/run/secrets/aicontrols
volumes:
- name: proxy-token
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600
audience: aicontrols # must match clusterOIDCAudience in values.yaml

Uninstalling

helm uninstall aicontrols -n aicontrols

The bootstrap Secret (aicontrols-admin-bootstrap) and the PersistentVolumeClaim are kept after uninstall. Delete them manually if you want a clean slate:

kubectl delete secret aicontrols-admin-bootstrap -n aicontrols
kubectl delete pvc -n aicontrols -l app.kubernetes.io/name=aicontrols
kubectl delete namespace aicontrols

Next steps

  • Agent Identity — assign each agent a unique ServiceAccount identity and configure dual identity for user-delegated agents
  • Write a Policy — create CEL rules for your governance requirements
  • Set a Budget — configure per-agent token and cost limits
  • Configuration Model — how deployment settings (Helm) and app settings (Settings UI) stay in sync across upgrades