On-Prem Deployment
Run the entire Willow platform inside your own Kubernetes cluster. The runtime, admin console, connect service, and database all run on your infrastructure — fully isolated from Willow SaaS, with no call-home requirement.
Why On-Prem?
- Complete data isolation — tool execution, authentication, audit logs, and configuration never leave your network
- No external dependencies at runtime — suitable for air-gapped environments and networks with strict egress controls
- Compliance — satisfies the strictest data residency and sovereignty requirements (HIPAA, FedRAMP, financial services, internal security policy)
- Full administrative control — you own the deployment lifecycle: upgrades, scaling, backups, and configuration
How It Works
All Willow microservices are deployed into your Kubernetes cluster with a single Helm chart:
┌──────────────────────────────────────────────────────────┐
│ Your Kubernetes cluster │
│ │
│ ┌──────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ app │──▶│ db-service │◀──│ connect │ │
│ │ (admin) │ └──────┬──────┘ │ (dashboard, │ │
│ └──────────┘ │ │ OAuth) │ │
│ ▼ └─────────────────┘ │
│ ┌────────────┐ ▲ │
│ │ PostgreSQL │ │ │
│ └────────────┘ ┌──────┴──────┐ │
│ MCP clients ─────────────────────▶│ run │ │
│ (Claude, Cursor) │ (tool exec) │ │
│ └─────────────┘ │
└──────────────────────────────────────────────────────────┘
Managed entirely by you
The Helm chart deploys:
- app — the administrative console for managing your Willow instance
- connect — the dashboard UI and OAuth / external tool authentication service
- run — the MCP runtime that executes tool calls inside your network
- db-service — the database access layer
- PostgreSQL (optional) — in-cluster database when
webrix-postgresql.enabled: true - Ingress, Service Accounts & RBAC — routing and the permissions required to operate
Your users connect their AI assistants (Claude, Cursor, or any MCP-compatible client) directly to the on-prem run endpoint. Authentication is handled by the on-prem connect service, which integrates with your existing SSO provider.
Setup Guide
Prerequisites
- Kubernetes cluster — EKS, GKE, AKS, OpenShift, or any conformant distribution (v1.23+ recommended)
- kubectl — configured to access your cluster
- Helm — v3+
- Domain name — with the ability to configure DNS records, and an ingress controller running in your cluster
Network Requirements
On-Prem has no call-home requirement — once deployed, it operates entirely within your network boundary. The only outbound traffic you may need to allow is:
- During install — access to the image registry (
quay.io/webrix) to pull images. For air-gapped clusters, mirror the images to your internal registry (see Custom Image Pull Secrets). - At runtime — only to the third-party APIs your tools actually call (GitHub, Slack, Jira, etc.). If you use AI-powered guardrails, also to your configured LLM provider.
Step 1 — Add the Helm Repository
helm repo add webrix https://webrix-ai.github.io/webrix-helm
helm repo update
Verify the repository was added:
helm search repo webrix
Step 2 — Create values.yaml
Create a values.yaml to configure your installation. This overrides the chart defaults to match your environment. The minimal configuration below runs the full suite with an in-cluster PostgreSQL database:
global:
# Base domain for your services. Default subdomains:
# app → willow-admin.<host>
# connect → willow-dashboard.<host>
# run → willow.<host>
domain:
host: "<YOUR_DOMAIN>" # e.g. example.com
# Optional — required only if you use AI-powered guardrails
OPENAI_API_KEY: ""
# In-cluster database. Deploys the webrix-postgres chart and wires
# db-service to it automatically.
webrix-postgresql:
enabled: true
The chart supports in-cluster PostgreSQL or an external database:
webrix-postgresql.enabled: true— the webrix-postgres chart (recommended default shown above)externalDatabase.url.*— bring your own database (see External Database)
Enable only one.
You can view every available value, setting, and default in the Helm chart repository: github.com/webrix-ai/webrix-helm
Configuration reference
| Value | Purpose |
|---|---|
global.domain.host | Your base domain. Used to build the ingress hostnames for each service. |
global.OPENAI_API_KEY | Optional. Required only if you use AI-powered guardrails. |
webrix-postgresql.enabled | Deploys the in-cluster webrix-postgres database. Recommended for in-cluster deployments. |
externalDatabase.url.* | Bring your own database. Disable both in-cluster options. See External Database. |
Step 3 — Install
Deploy with helm upgrade --install. Replace <namespace> with your desired namespace (e.g. webrix):
helm upgrade --install webrix webrix/webrix-helm \
--namespace <namespace> \
--create-namespace \
-f values.yaml \
--wait
Installation typically takes 2–5 minutes depending on your cluster.
Step 4 — Verify
Pod health:
kubectl get pods -n <namespace>
Expected output:
NAME READY STATUS RESTARTS AGE
app-xxxxxxxxxx-xxxxx 1/1 Running 0 2m
connect-xxxxxxxxxx-xxxxx 1/1 Running 0 2m
run-xxxxxxxxxx-xxxxx 1/1 Running 0 2m
db-service-xxxxxxxxxx-xxxxx 1/1 Running 0 2m
webrix-postgresql-0 1/1 Running 0 2m
Services and ingress:
kubectl get services -n <namespace>
kubectl get ingress -n <namespace>
Logs for a specific component:
kubectl logs -n <namespace> deployment/run --tail=100 -f
Step 5 — Set Up DNS and Access Your Services
Point DNS A records (or CNAMEs) at your ingress controller's external IP. Find it with:
kubectl get svc -n ingress-nginx # Adjust namespace for your ingress controller
Create the records:
willow-admin.<domain> → <ingress-ip>
willow-dashboard.<domain> → <ingress-ip>
willow.<domain> → <ingress-ip>
Once DNS resolves, your services are available at:
- App —
https://willow-admin.<domain>— administrative console - Dashboard —
https://willow-dashboard.<domain>— analytics and OAuth flows - Run —
https://willow.<domain>— MCP runtime endpoint your AI assistants connect to
Advanced
External Database
To use your own PostgreSQL, use externalDatabase.url:
externalDatabase:
url:
# Provide the DATABASE_URL via one of the following:
secretName: "webrix-db-url" # existing Secret with a DATABASE_URL key (recommended)
# clearText: "" # plain-text URL, stored in a ConfigMap (not recommended)
Prefer secretName (or sealedSecret) over clearText so credentials never land in plaintext values or a ConfigMap. Create the secret first:
kubectl create secret generic webrix-db-url \
--namespace <namespace> \
--from-literal=DATABASE_URL="postgres://user:password@db-host:5432/willow?sslmode=require"
Working with Custom Secrets
You'll typically mount custom Kubernetes secrets when you need to provide:
- External database credentials — when using
db_provider: "external" - Third-party API keys & SSO secrets — e.g. your SSO client secret,
OPENAI_API_KEY, etc.
Step 1 — Create the secret:
kubectl create secret generic app-api-keys \
--namespace <namespace> \
--from-literal=AUTH_OKTA_SECRET=xxxxxxxxxxxxx \
--from-literal=OPENAI_API_KEY=sk-xxxxxxxxxxxxx
Verify it was created:
kubectl get secret app-api-keys -n <namespace>
Step 2 — Reference it in values.yaml:
deployments:
app:
secretName: "app-api-keys"
All key-value pairs from the secret are mounted as environment variables in the app deployment. You can also use global.secretName to share a secret across all services, or sealedSecrets for encrypted secret management.
Step 3 — Redeploy:
helm upgrade webrix webrix/webrix-helm \
--namespace <namespace> \
-f values.yaml \
--wait
Multiple Gateways (Internal + External Access)
Some organizations need two separate access points to run — for example, one for VPN users (internal network) and one for non-VPN users (public internet). This is achieved with additional Ingress resources that use a different ingress controller. Both route to the same pods — no resource duplication.
Prerequisites: two ingress controllers in your cluster, each with a different ingressClassName (e.g. nginx-internal and nginx-external).
deployments:
run:
ingress:
enabled: true
className: "nginx-internal" # VPN users
subdomain: "willow"
path: "/"
pathType: "ImplementationSpecific"
ingresses:
external:
enabled: true
className: "nginx-external" # Non-VPN users
subdomain: "willow-ext"
path: "/"
pathType: "ImplementationSpecific"
This creates two Ingress resources for run:
| Ingress Name | Hostname | Ingress Controller |
|---|---|---|
run | willow.<domain> | nginx-internal (VPN) |
run-external | willow-ext.<domain> | nginx-external (Public) |
Point each hostname to its respective controller's IP:
willow.<domain> → <internal-ingress-ip>
willow-ext.<domain> → <external-ingress-ip>
Autoscaling (HPA)
Each service can scale automatically with a HorizontalPodAutoscaler. Autoscaling is off by default (services run at their fixed replicas count) — enable it per service under deployments.<service>.autoscaling:
deployments:
run:
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
Requirements: the metrics-server must be installed in your cluster, and each autoscaled service must define CPU resource requests (the chart's defaults already do).
Notes:
- CPU-based by default. Memory-based autoscaling is intentionally disabled — Node/V8 and model workloads hold memory high regardless of load, so a memory target tends to scale up and never scale back down. Opt in per service with
targetMemoryUtilizationPercentageif you understand the trade-off. db-serviceconnection limits. Eachdb-servicepod opens up to ~10 PostgreSQL connections. Keep itsmaxReplicaslow enough that peak connections (maxReplicas × 10) stay well under your database'smax_connections. Raise both together, not justmaxReplicas.- Advanced tuning. You can set a custom scaling
behaviorblock per service to control scale-up/scale-down rates.
Custom Image Pull Secrets
If your cluster doesn't already have access to the quay.io/webrix registry, create an image pull secret:
kubectl create secret docker-registry webrix-registry \
--namespace <namespace> \
--docker-server=quay.io \
--docker-username=<robot-username> \
--docker-password=<robot-token> \
--docker-email=unused@webrix.io
The chart references webrix-registry by default. To use a different name, set global.imagePullSecrets in your values.yaml.
TLS / Custom CA
If your network uses TLS inspection with a private certificate authority, add the CA certificate so the services trust internal endpoints:
global:
caCertificate: |
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgI...
-----END CERTIFICATE-----
The chart mounts the certificate and sets NODE_EXTRA_CA_CERTS automatically.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Pods stuck in Pending | Insufficient cluster resources | Check node capacity: kubectl describe pod <pod> -n <namespace> |
ImagePullBackOff | No registry access | Verify the webrix-registry pull secret (see Custom Image Pull Secrets) |
| Pod crashloops on startup | Configuration error in values.yaml | Review pod logs: kubectl logs <pod> -n <namespace> |
| Cannot access services | DNS not pointing to ingress | Verify DNS records resolve to the ingress controller's external IP |
| Ingress returns 404 / no host | Ingress controller not installed | Ensure an ingress controller is running and the className matches |
| TLS certificate errors | Certificate not configured / trusted | Check certificate config; for private CAs see TLS / Custom CA |
| Database connection failures | Wrong credentials or unreachable DB | In-cluster: kubectl logs webrix-postgresql-0 -n <namespace>. External: verify the DATABASE_URL secret values and network connectivity |
Inspecting Deployments
kubectl get pods -n <namespace>
kubectl describe pod <pod-name> -n <namespace>
# Component logs
kubectl logs -n <namespace> deployment/app --tail=100 -f
kubectl logs -n <namespace> deployment/connect --tail=100 -f
kubectl logs -n <namespace> deployment/run --tail=100 -f
kubectl logs -n <namespace> deployment/db-service --tail=100 -f
Helm Operations
# List releases
helm list -n <namespace>
# Release history
helm history webrix -n <namespace>
# Roll back to a previous revision
helm rollback webrix <revision> -n <namespace>
Getting Help
If you continue to experience issues, contact Willow support with:
- Kubernetes version:
kubectl version - Helm version:
helm version - Pod status and logs
- Your
values.yaml(redact sensitive information)