Self-Hosted Cloud: Deploying with Kubernetes
Deploy and manage Self-Hosted Pool workers using a Kubernetes operator. It handles scaling, rolling updates, and lifecycle management.

Prerequisites
- A Kubernetes cluster (v1.24+), either a cloud cluster (EKS, GKE, AKS) or a local one via OrbStack for testing
kubectlconfigured to talk to your clusterhelmv3 installed- A Cursor Enterprise plan with Self-Hosted Cloud Agents enabled
- A service account API key for worker authentication
If using OrbStack locally, switch to its context first:
kubectl config use-context orbstackYour cluster needs outbound HTTPS access to:
api2.cursor.shapi2direct.cursor.shdownloads.cursor.com
No inbound ports or firewall rules are required.
Step 1: Install the controller
helm upgrade --install worker-set-controller \ oci://public.ecr.aws/j6w0t2f5/cursor/worker-set-controller-chart \ --namespace cursord --create-namespace \ --version 0.1.0-6c804a0 \ --set imageTag=6c804a0 \ --set env.enableAuthManagement=truespec.auth only works when the controller starts with --enable-auth-management=true, which enables secret management from the controller. In Helm, set env.enableAuthManagement=true.
By default, the controller watches WorkerDeployment resources across all namespaces and the chart creates cluster-wide controller RBAC. To run the controller only in the release namespace, add --set rbac.singleNamespace=true:
helm upgrade --install worker-set-controller \ oci://public.ecr.aws/j6w0t2f5/cursor/worker-set-controller-chart \ --namespace cursord --create-namespace \ --version 0.1.0-6c804a0 \ --set imageTag=6c804a0 \ --set env.enableAuthManagement=true \ --set rbac.singleNamespace=trueIn single-namespace mode, the chart renders a namespace-scoped Role and RoleBinding instead of the controller ClusterRole and ClusterRoleBinding, then starts the controller with --watch-namespace=<release namespace>. The WorkerDeployment CRD is still cluster-scoped. If the installer shouldn't manage cluster-scoped resources, install the CRD separately and set crd.install=false.
Verify the controller is running:
kubectl -n cursord rollout status deployment/worker-set-controllerStep 2: Create the auth secret
Create a Kubernetes secret and bind it to the WorkerDeployment by label:
kubectl create secret generic my-workers-api-key \ --from-literal=api-key='YOUR_SERVICE_ACCOUNT_API_KEY' \ -n cursordkubectl label secret my-workers-api-key -n cursord \ workers.cursor.com/worker-deployment=my-workersThe label value must exactly match metadata.name on the WorkerDeployment. The controller refuses to read unbound secrets.
Kubernetes workers use this secret through controller-managed auth. The controller exchanges the key for a short-lived token and mounts it at:
/var/run/cursor/tokenThe worker authenticates with --auth-token-file /var/run/cursor/token. When the token expires or the connection drops, the CLI reads this file again before reconnecting. This lets the controller rotate tokens without restarting the pod.
Step 3: Create a worker pool
Your worker image must have:
agentCLI installedgitinstalled and available onPATH- A cloned repository with a configured remote as the working directory
To install the CLI:
curl https://cursor.com/install -fsS | bashInside the WorkerDeployment pod, start the worker with --pool, --idle-release-timeout, and the controller-managed token file:
agent worker \ --pool \ --idle-release-timeout 600 \ --auth-token-file /var/run/cursor/token \ start--pool registers each worker for pool assignment, where each Cloud Agent session claims one worker at a time. --idle-release-timeout (in seconds) keeps the worker alive briefly after the session ends for follow-up messages, then exits with code 0 so the pod gets replaced.
Workers deployed on Kubernetes support the same hooks model as Self-Hosted
Pool. They run project hooks from
.cursor/hooks.json and, on Enterprise, also support team hooks and
enterprise-managed hooks.
Example WorkerDeployment (adapt to your own setup):
The sample resource request below is a placeholder. 1 CPU and 2Gi memory
are enough to boot the agent process, but you should size workers for the repo,
build, and test workload you expect them to run.
apiVersion: workers.cursor.com/v1kind: WorkerDeploymentmetadata: name: my-workers namespace: cursordspec: auth: apiKeySecretRef: name: my-workers-api-key key: api-key workerContainerName: worker readyReplicas: 5 template: metadata: labels: app: my-worker spec: containers: - name: worker image: YOUR_IMAGE command: ["agent"] args: - "worker" - "--pool" - "--idle-release-timeout" - "600" - "--worker-dir" - "/path/to/your/repo" - "--auth-token-file" - "/var/run/cursor/token" - "--management-addr" - "0.0.0.0:8080" - "start" readinessProbe: httpGet: path: /readyz port: 8080 periodSeconds: 5 livenessProbe: httpGet: path: /healthz port: 8080 periodSeconds: 10 resources: requests: cpu: "1" memory: 2GiApply the WorkerDeployment:
kubectl apply -f workers.yamlStep 4: Verify workers are running
Check the worker pool status:
kubectl get wd -n cursordNAME READY DESIRED TOTAL AVAILABLE REVISION AGEmy-workers 3 3 3 True a1b2c3d4e5f6g7h8 2mYour workers should also appear in the Self-Hosted section of your Cloud Agents dashboard.
Scaling
To change the number of workers, update readyReplicas in your WorkerDeployment:
kubectl patch wd my-workers -n cursord --type merge \ -p '{"spec":{"readyReplicas": 20}}'The controller scales in batches and never terminates busy workers. Pods actively processing agent work are preserved until they finish.
Rolling updates
When you change the pod template (e.g. update the image, environment variables, or resource limits), the controller performs a safe rolling update:
- Creates new-revision pods in batches
- Waits for them to become ready
- Drains old-revision pods that are idle
- Preserves old-revision pods that are busy until they finish
Agent sessions are never interrupted by deployments.
Worker labels
Use Cursor worker labels to make workers selectable for specific teams, repos, or environments. These are separate from Kubernetes pod labels. They're passed as --label flags to the agent CLI and control which workers are eligible for a given session.
Inside the WorkerDeployment pod, pass labels with the worker args:
agent worker \ --pool \ --idle-release-timeout 600 \ --auth-token-file /var/run/cursor/token \ --label team=backend \ --label env=production \ startUsers select which worker pool to use from the Cloud Agents dropdown in Cursor.
Health checks
The --management-addr flag in the WorkerDeployment exposes health check endpoints for Kubernetes readiness and liveness probes:
| Endpoint | 200 | 503 |
|---|---|---|
/healthz | Process is running | Never |
/readyz | Connected and idle | Starting up, or running a session |
The /readyz endpoint is the key integration point:
- A worker starts and connects.
/readyzreturns 200. - A session claims the worker.
/readyzflips to 503. Kubernetes removes it from the ready pool and spins up a replacement. - The session ends. The worker exits with code 0. The controller replaces the pod to maintain
readyReplicas.
Busy workers are never terminated, even during scale-down or rolling updates.