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" \
  --wait --timeout 10m

That is a complete install. Everything below explains what those values do and what to change for production.

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 or Microsoft Foundry. 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 decide how the platform behaves. 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.


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

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

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.