Skip to content

Install on Kubernetes#

Lens Agents installs into your own Kubernetes cluster from a Helm chart published to GHCR as an OCI artifact. There is no hosted control plane to send workloads to: the platform server, the sandboxes it runs, the database, and the audit trail all stay inside your cluster.

helm install lens-agents oci://ghcr.io/lensapp/lens-agents \
  --set encryption.key="$(openssl rand -hex 32)" \
  --set config.publicUrl="https://agents.example.com" \
  --set sandboxIngress.host="sandboxes.example.com" \
  --set inference.openrouter.token="<openrouter-api-key>" \
  --wait --timeout 10m

That is a complete install. Everything below explains what those values do and what to change for production. The provider on the last line is the shortest one to start with; four others take its place.

Want to try it first?

Try Lens Agents locally walks the same install on a single-node local cluster, with a first agent at the end. Come back here when you install on a real cluster.


Prerequisites#

Requirement Detail
Kubernetes A cluster you can install into, with a default storage class for the bundled database and sandbox volumes.
Helm v3.8 or later, for OCI chart support.
Node kernel with nf_tables Sandboxes lock their network down with nftables. A node whose kernel lacks nf_tables runs the platform but fails every sandbox.
Outbound network access To pull images from GHCR and to complete activation against Lens Cloud.
A Lens ID The account that activates the install becomes its owner. Sign up at app.k8slens.dev.
An inference provider credential AWS Bedrock, Microsoft Foundry, OpenAI, or an OpenRouter API key. See Inference providers.

Managed cloud Kubernetes (EKS, AKS, GKE) and self-managed distributions carry nf_tables in their node kernels. Local clusters that run the node as a container on your host kernel — kind, Docker Desktop Kubernetes, minikube --driver=docker — also work. minikube's VM drivers ship an ISO kernel built without nf_tables and cannot run sandboxes.


Required values#

Three values are required, and one more decides whether agents can answer at all. The rest have workable defaults.

encryption.key#

A 64-character hex key used to encrypt secrets at rest — credentials, connector tokens, AWS keys. Generate one with openssl rand -hex 32.

Losing this key makes every stored secret unreadable. Store it the way you store any other root secret, and prefer handing the platform a Kubernetes Secret over passing the key on the command line:

kubectl create secret generic lens-agents-encryption \
  --from-literal=encryptionKey="$(openssl rand -hex 32)"

helm install lens-agents oci://ghcr.io/lensapp/lens-agents \
  --set encryption.existingSecret=lens-agents-encryption \
  --set config.publicUrl="https://agents.example.com"

encryption.existingSecretKey names the key inside that Secret and defaults to encryptionKey.

config.publicUrl#

The URL the platform answers on. It is not cosmetic — the platform builds OAuth redirect URLs, sandbox ingress URLs, and activation callbacks from it. Set it to the address users and agents will actually reach, including the scheme.

sandboxIngress.host#

The wildcard DNS parent for sandbox exposed ports. A sandbox that publishes a port becomes reachable at <slug>.<sandboxIngress.host>, so *.sandboxes.example.com must resolve to your ingress.

Leave it empty and sandboxes still run, but exposed ports resolve to no URL.

An inference provider#

Agents reach their model through the platform's managed inference proxy, which is what makes spending limits, PII masking, and per-call audit possible. An install with no provider configured comes up healthy and runs sandboxes, but no agent in it answers.

Configure at least one:

Provider Minimum values What it serves
OpenRouter inference.openrouter.token Roughly 400 models from around 60 vendors, on either wire format. No cloud account, IAM role, or region to decide first.
AWS Bedrock inference.bedrock.token, or nothing at all where the platform's service account assumes an IAM role Claude. A token also exposes Bedrock Mantle, which serves Claude and GPT off the same key.
Microsoft Foundry inference.azure.baseUrl and inference.azure.token Claude and GPT from one resource and key.
OpenAI inference.openai.token GPT. inference.openai.baseUrl points the backend at any OpenAI-compatible endpoint instead.

Every provider also accepts its token from a Kubernetes Secret, which is what to do outside a first install:

kubectl create secret generic lens-agents-openrouter \
  --from-literal=NEXUS_OPENROUTER_TOKEN="<openrouter-api-key>"

helm upgrade lens-agents oci://ghcr.io/lensapp/lens-agents \
  --reuse-values \
  --set inference.openrouter.existingSecret=lens-agents-openrouter

--reuse-values is what keeps the values you already set. Without it, Helm resets every value this command does not name back to the chart default, encryption.key included.

Configuring a provider makes it available. A policy has to enable managed inference and name that provider before any sandbox can use it. Inference providers covers all five backends, what each one seeds into a sandbox, and why a sandbox allowed to dial a provider host directly is metered by nothing.

config.ownerEmails#

Not required, and worth setting before anyone signs in.

Value Default Purpose
config.ownerEmails [] The people who run this installation, by email. Empty names no owners, which leaves organization creation open to anyone who can sign in.
--set 'config.ownerEmails={ops@acme.com,admin@acme.com}'

Name owners and only they may create organizations; everyone else joins one by invitation, and a signed-in person with no organization, no invitation, and no right to create one is refused rather than given an account. Behind a corporate identity provider that is the difference between your platform team having accounts and your whole company having them. See Installation owners for the admission rules and the IdP claim it depends on.


Database#

The chart bundles PostgreSQL 17 and enables it by default. That is fine for evaluation and small installs, but the bundled instance ships with a default password and a single 8Gi volume.

For production, point the platform at a database you operate:

kubectl create secret generic lens-agents-db \
  --from-literal=DATABASE_URL="postgres://user:pass@db.example.com:5432/lens_agents"

helm upgrade --install lens-agents oci://ghcr.io/lensapp/lens-agents \
  --set postgresql.enabled=false \
  --set externalDatabase.existingSecret=lens-agents-db \
  ...

To keep the bundled database but harden it, set postgresql.auth.password, size postgresql.persistence.size, and name a postgresql.persistence.storageClass that your cluster backs up.


Ingress#

The platform listens on port 3002 behind a ClusterIP service. Expose it with the chart's ingress, a Gateway API HTTPRoute, or your own resource in front of the service.

--set ingress.enabled=true \
--set ingress.className=nginx \
--set ingress.hosts[0].host=agents.example.com \
--set ingress.tls[0].secretName=lens-agents-tls \
--set ingress.tls[0].hosts[0]=agents.example.com

Set httpRoute.enabled=true with httpRoute.parentRefs instead if you run Gateway API.

Sandbox exposed ports need a second, wildcard route for *.<sandboxIngress.host> pointing at the same service. The platform resolves the sandbox from the subdomain and enforces the port's auth mode before proxying.

The platform holds long-lived WebSocket connections

Sandbox tunnels, exec sessions, and the admin UI all use WebSockets. Configure your ingress with generous read and send timeouts, or exec sessions drop mid-command.


Sandbox runtime#

sandbox.k8s controls how sandboxes are scheduled.

Value Default Purpose
sandbox.k8s.cpu 500m Ceiling for a sandbox's CPU request. A sandbox spec asking for more is rejected.
sandbox.k8s.memory 2Gi Ceiling for a sandbox's memory.
sandbox.k8s.activeDeadlineSeconds 3600 Wall-clock lifetime before a sandbox pod is stopped.
sandbox.k8s.runtimeClassName (empty) Runtime class for sandbox pods. Set it to a Kata Containers or gVisor class for per-sandbox microVM or user-space kernel isolation.
sandbox.k8s.workspaceStorageClass (cluster default) Storage class for sandbox workspaces.
sandbox.k8s.agentVolumeStorageClass (cluster default) Storage class for persistent agent volumes.
sandboxNamespace.name <release>-sandbox Namespace sandboxes are created in.

Raise sandbox.k8s.cpu and sandbox.k8s.memory before running agents that build software or hold large working sets — the ceilings apply to every sandbox in the install.

sandbox.k8s.runtimeClassName is the single highest-value hardening step. Without it, sandboxes get privilege dropping, kernel-level network lockdown, and proxy-mediated egress, but share the node kernel. With a Kata runtime class, each sandbox gets its own kernel. See Sandbox isolation.


Verify the install#

kubectl rollout status deploy/lens-agents --timeout=5m
kubectl get pods
curl -fsS https://agents.example.com/health && echo OK

The platform serves three surfaces once it is up:

Surface Path Used by
Admin UI / Platform administrators
REST API /v1 nexusctl, scripts, CI
MCP endpoint /mcp Desktop AI tools and external agents
CLI installer /install.sh Anyone installing nexusctl

The OpenAPI document for the running version is served at /v1/openapi.json.

The installer and the binaries it downloads are unauthenticated by necessity — you hold no credentials until the CLI that obtains them exists. They serve a build matching this platform's version, so an air-gapped install needs no package registry.

A fresh install boots unactivated and shows an activation screen instead of a login. Continue to Activation.


Upgrade#

helm upgrade lens-agents oci://ghcr.io/lensapp/lens-agents --reuse-values --wait

Database migrations run at startup. Sandboxes keep running across a platform upgrade; they reconnect to the new server process when it is ready.


Sizing a production cluster, air-gapped installs, and hardened runtime classes are worth a conversation. Talk to us.