Deploy AgentWatch on Your Cluster
Run AgentWatch entirely inside your own infrastructure. Agent telemetry, guardrail logs, and run data never leave your servers. Supports AWS EKS, GCP GKE, Azure AKS, and bare-metal on-prem clusters.
What gets deployed
AgentWatch runs as four services inside a dedicated agentwatch namespace. Nothing else is modified in your cluster.
Prerequisites
| Requirement | Minimum | Recommended (production) |
|---|---|---|
| Kubernetes version | 1.24+ | 1.28+ |
| Node RAM | 8 GB | 16–32 GB |
| Node CPU | 4 vCPU | 8–16 vCPU |
| Persistent storage | 60 GB | 200 GB+ (SSD) |
| kubectl | v1.24+ | matches cluster version |
| Helm (optional) | 3.12+ | 3.14+ |
| nginx-ingress-controller | Any version | v1.8+ |
| cert-manager (for TLS) | v1.12+ | v1.13+ |
Quickstart — Helm (recommended)
Fastest path. Creates the namespace, generates secrets, and deploys all four services from the chart in this repo (helm/agentwatch/). Images are pulled from GHCR.
Install prerequisites
nginx-ingress-controller and cert-manager handle traffic routing and TLS.
1# nginx-ingress-controller2kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml34# cert-manager5kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml67# Wait for cert-manager to be ready8kubectl wait --for=condition=ready pod -l app=cert-manager -n cert-manager --timeout=90s
Create image pull secret
AgentWatch images are on a private GHCR registry. Your cluster needs read credentials.
ghcr.io/chinmaynayar.1kubectl create namespace agentwatch23kubectl create secret docker-registry ghcr-credentials \4 --docker-server=ghcr.io \5 --docker-username=YOUR_USERNAME \6 --docker-password=YOUR_READ_TOKEN \7 --namespace agentwatch
Create cert-manager ClusterIssuer
Automatic TLS via Let's Encrypt.
1apiVersion: cert-manager.io/v12kind: ClusterIssuer3metadata:4 name: letsencrypt-prod5spec:6 acme:7 server: https://acme-v02.api.letsencrypt.org/directory8 email: YOUR_EMAIL@company.com9 privateKeySecretRef:10 name: letsencrypt-prod11 solvers:12 - http01:13 ingress:14 class: nginx
1kubectl apply -f cluster-issuer.yaml
Deploy with Helm
Replace YOUR_DOMAIN with the parent domain (e.g. acme.com → app.acme.com + api.acme.com).
CONNECTOR_ENCRYPTION_KEY is required in production or the API exits on boot.1# Generate secrets (copy and save these)2PG_PASS=$(openssl rand -hex 20)3API_KEY=$(openssl rand -hex 16)4INT_SECRET=$(openssl rand -hex 32)5FERNET_KEY=$(python3 -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')6NEXT_SECRET=$(openssl rand -base64 32 | tr -d '\n')78echo "POSTGRES_PASSWORD=$PG_PASS"9echo "AGENTWATCH_DEV_API_KEY=$API_KEY"10echo "INTERNAL_AUTH_SECRET=$INT_SECRET"11echo "CONNECTOR_ENCRYPTION_KEY=$FERNET_KEY"12echo "NEXTAUTH_SECRET=$NEXT_SECRET"1314# Clone + deploy the chart from this repo15git clone https://github.com/chinmayNayar/agentwatch.git16cd agentwatch1718helm install agentwatch helm/agentwatch/ \19 --namespace agentwatch --create-namespace \20 --set global.domain="YOUR_DOMAIN" \21 --set secrets.postgresPassword="$PG_PASS" \22 --set secrets.agentwatchDevApiKey="$API_KEY" \23 --set secrets.internalAuthSecret="$INT_SECRET" \24 --set secrets.connectorEncryptionKey="$FERNET_KEY" \25 --set secrets.nextauthSecret="$NEXT_SECRET" \26 --set imagePullSecret="ghcr-credentials"
Point DNS to your cluster
Get the ingress IP and create two DNS records.
1# Get the ingress external IP (may take 2–3 minutes after deploy)2kubectl get svc -n ingress-nginx ingress-nginx-controller \3 -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
Create two A records pointing to that IP (on AWS NLB use CNAMEs to the hostname instead):
Verify the installation
1# Watch pods come up (takes 3–5 minutes)2kubectl get pods -n agentwatch -w34# All should show: STATUS=Running, READY=1/156# Health check7kubectl port-forward svc/api-service 8000:8000 -n agentwatch &8curl http://localhost:8000/health9# Expected: {"status":"ok","db":true,"redis":true}
{"status":"ok","db":true,"redis":true}, AgentWatch is live. Open https://app.YOUR_DOMAIN for the console. For a custom domain, rebuild the dashboard image — see Customer dashboard image.Alternative — kubectl / Kustomize
If your team prefers raw manifests, apply the Kustomize overlay in k8s/. Secrets are a template and are not included in kustomization.yaml.
1# Clone the manifests2git clone https://github.com/chinmayNayar/agentwatch.git3cd agentwatch45# Create namespace + image pull secret (same as Helm step 2)6kubectl create namespace agentwatch7kubectl create secret docker-registry ghcr-credentials \8 --docker-server=ghcr.io --docker-username=YOUR_USERNAME \9 --docker-password=YOUR_READ_TOKEN --namespace agentwatch1011# Fill in secrets (copy template, fill values, apply, then delete local copy)12cp k8s/secrets/agentwatch-secrets.yaml /tmp/aw-secrets.yaml13# Edit /tmp/aw-secrets.yaml with real values — never commit them14kubectl apply -f /tmp/aw-secrets.yaml && rm /tmp/aw-secrets.yaml1516# Edit domain (placeholders are RFC 1123: example.com, not CUSTOMER_DOMAIN)17sed -i '' 's/app.example.com/app.YOUR_DOMAIN/g' k8s/ingress/ingress.yaml18sed -i '' 's/api.example.com/api.YOUR_DOMAIN/g' k8s/ingress/ingress.yaml19# Linux: drop the '' after -i2021# Deploy everything22kubectl apply -k k8s/23kubectl get pods -n agentwatch -w
Storage Classes & PVCs
AgentWatch uses two PersistentVolumeClaims. Both use ReadWriteOnce (RWO) — they attach to a single node, which is correct for databases. You do not need ReadWriteMany (RWX).
| PVC | Access Mode | Default size | Why |
|---|---|---|---|
| postgres-pvc | RWO | 50 Gi | TimescaleDB data + WAL. SSD required. Grows ~3–5GB/month at 10M steps/mo. |
| redis-pvc | RWO | 5 Gi | Redis AOF-style persistence. Rarely exceeds 1GB. |
StorageClass by cloud
Use gp3 (faster and cheaper than gp2). Install the EBS CSI driver if it is not already present.
1# Install EBS CSI driver (required for EKS 1.23+)2eksctl create addon --name aws-ebs-csi-driver --cluster YOUR_CLUSTER --region YOUR_REGION34# Create a gp3 StorageClass and set as default5kubectl apply -f - <<EOF6apiVersion: storage.k8s.io/v17kind: StorageClass8metadata:9 name: gp310 annotations:11 storageclass.kubernetes.io/is-default-class: "true"12provisioner: ebs.csi.aws.com13parameters:14 type: gp315 iops: "3000"16 throughput: "125"17 encrypted: "true"18volumeBindingMode: WaitForFirstConsumer19reclaimPolicy: Retain20allowVolumeExpansion: true21EOF
1helm install agentwatch helm/agentwatch/ \2 --namespace agentwatch --create-namespace \3 --set postgres.storageClass="gp3" \4 --set redis.storageClass="gp3"
WaitForFirstConsumer creates the PVC in the same AZ as the pod. Without it, pods can fail with volume node affinity conflict.AWS EKS — Full Deployment Guide
1# 1. Create EKS cluster (if you don't have one)2eksctl create cluster \3 --name agentwatch-prod \4 --region us-east-1 \5 --nodegroup-name standard \6 --node-type m5.xlarge \7 --nodes 2 \8 --nodes-min 2 \9 --nodes-max 4 \10 --managed1112# 2. Install EBS CSI driver13eksctl create addon \14 --name aws-ebs-csi-driver \15 --cluster agentwatch-prod \16 --region us-east-11718# 3. Install nginx-ingress (AWS NLB mode)19helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx20helm install ingress-nginx ingress-nginx/ingress-nginx \21 --namespace ingress-nginx --create-namespace \22 --set controller.service.annotations."service\.beta\.kubernetes\.io/aws-load-balancer-type"="nlb"2324# 4. Install cert-manager25kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml2627# 5. Get NLB hostname (use this for DNS CNAME, not A record)28kubectl get svc -n ingress-nginx ingress-nginx-controller \29 -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'3031# 6. Deploy AgentWatch (from a clone of this repo)32helm install agentwatch helm/agentwatch/ \33 --namespace agentwatch --create-namespace \34 --set global.domain="agentwatch.company.com" \35 --set postgres.storageClass="gp3" \36 --set redis.storageClass="gp3" \37 --set imagePullSecret="ghcr-credentials" \38 --set secrets.postgresPassword="$(openssl rand -hex 20)" \39 --set secrets.agentwatchDevApiKey="$(openssl rand -hex 16)" \40 --set secrets.internalAuthSecret="$(openssl rand -hex 32)" \41 --set secrets.connectorEncryptionKey="$(python3 -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')" \42 --set secrets.nextauthSecret="$(openssl rand -base64 32 | tr -d '\n')"
GCP GKE — Full Deployment Guide
1# 1. Create GKE cluster2gcloud container clusters create agentwatch-prod \3 --region us-central1 \4 --machine-type n2-standard-4 \5 --num-nodes 2 \6 --enable-autoscaling --min-nodes 2 --max-nodes 4 \7 --workload-pool=$(gcloud config get-value project).svc.id.goog89# 2. Get credentials10gcloud container clusters get-credentials agentwatch-prod --region us-central11112# 3. Install nginx-ingress13helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx14helm install ingress-nginx ingress-nginx/ingress-nginx \15 --namespace ingress-nginx --create-namespace1617# 4. Install cert-manager18kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml1920# 5. Get ingress IP21kubectl get svc -n ingress-nginx ingress-nginx-controller \22 -o jsonpath='{.status.loadBalancer.ingress[0].ip}'23# → Create A records: app.YOUR_DOMAIN → IP, api.YOUR_DOMAIN → IP2425# 6. Deploy AgentWatch26helm install agentwatch helm/agentwatch/ \27 --namespace agentwatch --create-namespace \28 --set global.domain="agentwatch.company.com" \29 --set postgres.storageClass="pd-ssd" \30 --set redis.storageClass="pd-ssd" \31 --set imagePullSecret="ghcr-credentials" \32 --set secrets.postgresPassword="$(openssl rand -hex 20)" \33 --set secrets.agentwatchDevApiKey="$(openssl rand -hex 16)" \34 --set secrets.internalAuthSecret="$(openssl rand -hex 32)" \35 --set secrets.connectorEncryptionKey="$(python3 -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')" \36 --set secrets.nextauthSecret="$(openssl rand -base64 32 | tr -d '\n')"
Azure AKS — Full Deployment Guide
1# 1. Create resource group and AKS cluster2az group create --name agentwatch-rg --location eastus3az aks create \4 --resource-group agentwatch-rg \5 --name agentwatch-prod \6 --node-count 2 \7 --node-vm-size Standard_D4s_v3 \8 --enable-cluster-autoscaler \9 --min-count 2 --max-count 4 \10 --generate-ssh-keys1112# 2. Get credentials13az aks get-credentials --resource-group agentwatch-rg --name agentwatch-prod1415# 3. Install nginx-ingress16helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx17helm install ingress-nginx ingress-nginx/ingress-nginx \18 --namespace ingress-nginx --create-namespace1920# 4. Install cert-manager21kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml2223# 5. Get ingress IP24kubectl get svc -n ingress-nginx ingress-nginx-controller \25 -o jsonpath='{.status.loadBalancer.ingress[0].ip}'2627# 6. Deploy AgentWatch28helm install agentwatch helm/agentwatch/ \29 --namespace agentwatch --create-namespace \30 --set global.domain="agentwatch.company.com" \31 --set postgres.storageClass="managed-csi-premium" \32 --set redis.storageClass="managed-csi-premium" \33 --set imagePullSecret="ghcr-credentials" \34 --set secrets.postgresPassword="$(openssl rand -hex 20)" \35 --set secrets.agentwatchDevApiKey="$(openssl rand -hex 16)" \36 --set secrets.internalAuthSecret="$(openssl rand -hex 32)" \37 --set secrets.connectorEncryptionKey="$(python3 -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')" \38 --set secrets.nextauthSecret="$(openssl rand -base64 32 | tr -d '\n')"
On-Prem / Bare Metal — Full Deployment Guide
1# 1. Install k3s on your server (single-node)2curl -sfL https://get.k3s.io | sh -s - \3 --disable traefik \4 --write-kubeconfig-mode 64456export KUBECONFIG=/etc/rancher/k3s/k3s.yaml78# 2. Install local-path-provisioner (storage)9kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.26/deploy/local-path-storage.yaml10kubectl patch storageclass local-path \11 -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'1213# 3. Install nginx-ingress14helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx15helm install ingress-nginx ingress-nginx/ingress-nginx \16 --namespace ingress-nginx --create-namespace \17 --set controller.service.type=NodePort \18 --set controller.service.nodePorts.http=80 \19 --set controller.service.nodePorts.https=4432021# 4. Install cert-manager22kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml2324# 5. Point DNS to your server's public IP25# app.YOUR_DOMAIN → SERVER_IP26# api.YOUR_DOMAIN → SERVER_IP2728# 6. Deploy AgentWatch29helm install agentwatch helm/agentwatch/ \30 --namespace agentwatch --create-namespace \31 --set global.domain="agentwatch.company.com" \32 --set imagePullSecret="ghcr-credentials" \33 --set secrets.postgresPassword="$(openssl rand -hex 20)" \34 --set secrets.agentwatchDevApiKey="$(openssl rand -hex 16)" \35 --set secrets.internalAuthSecret="$(openssl rand -hex 32)" \36 --set secrets.connectorEncryptionKey="$(python3 -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')" \37 --set secrets.nextauthSecret="$(openssl rand -base64 32 | tr -d '\n')"
Secrets Reference
Pass these via Helm --set secrets.* or fill k8s/secrets/agentwatch-secrets.yaml (kustomize). Never commit real values.
| Variable | Required | Purpose & how to generate |
|---|---|---|
| POSTGRES_PASSWORD | Yes | Postgres superuser password. openssl rand -hex 20 |
| AGENTWATCH_DEV_API_KEY | Yes | API key for SDK + curl. openssl rand -hex 16 |
| INTERNAL_AUTH_SECRET | Yes | Dashboard → API auth. openssl rand -hex 32 |
| CONNECTOR_ENCRYPTION_KEY | Yes | Fernet key for Gate secrets. python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" |
| NEXTAUTH_SECRET | Yes | NextAuth session signing. openssl rand -base64 32 |
| RESEND_API_KEY | No | Optional. Email alerts and Book-a-Demo notifications. |
| GOOGLE_API_KEY | No | Optional. Agora (AI copilot) and Arena. |
| STRIPE_* | No | Optional. Billing if you enable Stripe on that install. |
LOCAL_NO_AUTH=false and AGENTWATCH_ENV=production. The API will not boot in production without CONNECTOR_ENCRYPTION_KEY.Ingress & TLS
The chart and kustomize overlay both expect nginx-ingress plus a cert-manager ClusterIssuer named letsencrypt-prod. Hosts are app.YOUR_DOMAIN and api.YOUR_DOMAIN. Helm sets these from global.domain. Kustomize ships placeholders app.example.com / api.example.com — Kubernetes rejects a host like app.CUSTOMER_DOMAIN.
1# Confirm ingress + cert2kubectl get ingress -n agentwatch3kubectl describe certificate -n agentwatch4kubectl get clusterissuer letsencrypt-prod
Customer dashboard image
Dashboard NEXT_PUBLIC_* values are baked in at image build time. The stock ghcr.io/chinmaynayar/agentwatch-dashboard:latest image points at agentwatch.in. For your domain, rebuild:
1# GitHub → Actions → "Build Customer Image" → Run workflow2# customer_slug: acme-bank3# api_url: https://api.YOUR_DOMAIN4# app_url: https://app.YOUR_DOMAIN5#6# Then:7helm upgrade agentwatch helm/agentwatch/ \8 --namespace agentwatch \9 --reuse-values \10 --set dashboard.imageTag="acme-bank-latest"
Air-Gapped Clusters
For clusters with no outbound internet (defence, regulated banking), transfer images offline, then point Helm at your internal registry.
1# On a machine WITH internet access:23docker pull ghcr.io/chinmaynayar/agentwatch-api:latest4docker pull ghcr.io/chinmaynayar/agentwatch-dashboard:latest5docker pull timescale/timescaledb:latest-pg156docker pull redis:7-alpine7docker pull postgres:15-alpine # API init container (pg_isready)8docker pull curlimages/curl:latest # dashboard init container910docker save ghcr.io/chinmaynayar/agentwatch-api:latest | gzip > agentwatch-api.tar.gz11docker save ghcr.io/chinmaynayar/agentwatch-dashboard:latest | gzip > agentwatch-dashboard.tar.gz12docker save timescale/timescaledb:latest-pg15 | gzip > timescaledb.tar.gz13docker save redis:7-alpine | gzip > redis.tar.gz14docker save postgres:15-alpine | gzip > postgres-init.tar.gz15docker save curlimages/curl:latest | gzip > curl-init.tar.gz1617# Transfer to the air-gapped node (USB, SCP to bastion, etc.)18scp *.tar.gz admin@airgapped-node:/tmp/1920# On the air-gapped node:21docker load < /tmp/agentwatch-api.tar.gz22docker load < /tmp/agentwatch-dashboard.tar.gz23docker load < /tmp/timescaledb.tar.gz24docker load < /tmp/redis.tar.gz25docker load < /tmp/postgres-init.tar.gz26docker load < /tmp/curl-init.tar.gz2728# Push to your internal registry29docker tag ghcr.io/chinmaynayar/agentwatch-api:latest registry.internal/agentwatch-api:latest30docker push registry.internal/agentwatch-api:latest31# Repeat for dashboard, timescaledb, redis, postgres:15-alpine, curlimages/curl3233# Deploy pointing at the internal registry34helm install agentwatch helm/agentwatch/ \35 --namespace agentwatch --create-namespace \36 --set global.domain="YOUR_DOMAIN" \37 --set global.registry="registry.internal" \38 --set postgres.image="registry.internal/timescaledb:latest-pg15" \39 --set redis.image="registry.internal/redis:7-alpine" \40 --set imagePullPolicy="IfNotPresent"
Verifying the Installation
1# 1. All pods must be Running 1/12kubectl get pods -n agentwatch3# NAME READY STATUS RESTARTS4# postgres-0 1/1 Running 05# redis-xxx 1/1 Running 06# agentwatch-api-xxx 1/1 Running 07# agentwatch-api-yyy 1/1 Running 08# agentwatch-dashboard-xxx 1/1 Running 0910# 2. API health (db AND redis must be true)11kubectl port-forward svc/api-service 8000:8000 -n agentwatch &12curl http://localhost:8000/health13# {"status":"ok","db":true,"redis":true}1415# 3. Create a test run to confirm end-to-end16API_KEY=$(kubectl get secret agentwatch-secrets -n agentwatch \17 -o jsonpath='{.data.AGENTWATCH_DEV_API_KEY}' | base64 -d)1819RUN=$(curl -s -X POST http://localhost:8000/runs \20 -H "x-api-key: $API_KEY" \21 -H "Content-Type: application/json" \22 -d '{"agent_name":"install-verify"}')23echo $RUN | python3 -m json.tool24# Should show run_id, status: running2526# 4. Open the dashboard27echo "Console: https://app.YOUR_DOMAIN"
db:true, redis:true, the install is complete. The dashboard at https://app.YOUR_DOMAIN shows the login page.Upgrading
1# Check current release2helm list -n agentwatch34# Pull latest chart + images, keep existing values5git pull6helm upgrade agentwatch helm/agentwatch/ \7 --namespace agentwatch \8 --reuse-values910# Watch the rollout11kubectl rollout status deployment/agentwatch-api -n agentwatch12kubectl rollout status deployment/agentwatch-dashboard -n agentwatch1314# Verify after upgrade15kubectl port-forward svc/api-service 8000:8000 -n agentwatch &16curl http://localhost:8000/health
Backup & Restore
1# Backup Postgres (from any machine with kubectl access)2kubectl exec -n agentwatch postgres-0 -- \3 pg_dump -U agentwatch agentwatch_db | gzip > agentwatch-backup-$(date +%Y%m%d).sql.gz45# Restore (to a fresh install)6gunzip -c agentwatch-backup-20260912.sql.gz | \7 kubectl exec -i -n agentwatch postgres-0 -- \8 psql -U agentwatch agentwatch_db
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Pod stuck in Pending | No StorageClass / PVC cannot bind | kubectl describe pod → Events. Install a StorageClass for your cluster type. |
| ErrImagePull / ImagePullBackOff | Pull secret missing or wrong | kubectl describe pod. Recreate ghcr-credentials with a valid read:packages token. |
| "db": false in /health | Postgres not ready or wrong DATABASE_URL | kubectl logs -n agentwatch postgres-0. Check POSTGRES_PASSWORD in the secret. |
| "redis": false in /health | Redis pod not running | kubectl get pods -n agentwatch. Check redis logs. |
| Ingress 404 / no route to host | DNS not pointing to ingress IP | kubectl get svc -n ingress-nginx. Confirm A/CNAME records match the LB. |
| TLS cert pending / not issued | ClusterIssuer missing or HTTP-01 failing | kubectl describe certificate -n agentwatch. Confirm letsencrypt-prod exists. |
| Dashboard blank / wrong API | NEXT_PUBLIC_API_URL baked for another domain | Rebuild the dashboard image with the Build Customer Image workflow. |
| volume node affinity conflict | EBS PVC in the wrong AZ (AWS) | Use WaitForFirstConsumer on the StorageClass. |
Need help with your deployment?
Our team will deploy AgentWatch in your cluster, verify health, and hand it off ready to use. Included with all on-prem licenses.
Book a deployment session →