Skip to main content

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
Choosing your database

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.

All configuration values

You can view every available value, setting, and default in the Helm chart repository: github.com/webrix-ai/webrix-helm

Configuration reference

ValuePurpose
global.domain.hostYour base domain. Used to build the ingress hostnames for each service.
global.OPENAI_API_KEYOptional. Required only if you use AI-powered guardrails.
webrix-postgresql.enabledDeploys 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:

  • Apphttps://willow-admin.<domain> — administrative console
  • Dashboardhttps://willow-dashboard.<domain> — analytics and OAuth flows
  • Runhttps://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 NameHostnameIngress Controller
runwillow.<domain>nginx-internal (VPN)
run-externalwillow-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 targetMemoryUtilizationPercentage if you understand the trade-off.
  • db-service connection limits. Each db-service pod opens up to ~10 PostgreSQL connections. Keep its maxReplicas low enough that peak connections (maxReplicas × 10) stay well under your database's max_connections. Raise both together, not just maxReplicas.
  • Advanced tuning. You can set a custom scaling behavior block 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

SymptomCauseFix
Pods stuck in PendingInsufficient cluster resourcesCheck node capacity: kubectl describe pod <pod> -n <namespace>
ImagePullBackOffNo registry accessVerify the webrix-registry pull secret (see Custom Image Pull Secrets)
Pod crashloops on startupConfiguration error in values.yamlReview pod logs: kubectl logs <pod> -n <namespace>
Cannot access servicesDNS not pointing to ingressVerify DNS records resolve to the ingress controller's external IP
Ingress returns 404 / no hostIngress controller not installedEnsure an ingress controller is running and the className matches
TLS certificate errorsCertificate not configured / trustedCheck certificate config; for private CAs see TLS / Custom CA
Database connection failuresWrong credentials or unreachable DBIn-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)