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.
Related#
- Activation — register the install and create the first administrator
- Inference providers — wire up Bedrock or Foundry so agents can reach a model
- Install the CLI — set up
nexusctlagainst your platform - Sandbox isolation — what the runtime class buys you
Sizing a production cluster, air-gapped installs, and hardened runtime classes are worth a conversation. Talk to us.