Run Cursor Self-Hosted Pool workers in Daytona container, linux-vm, or windows sandboxes. A macOS or Linux computer runs agent worker controller and the Python helper in this guide. The controller claims Cursor requests, creates one sandbox for each worker, and deletes the sandbox when the worker exits.
Cursor keeps the agent loop and model access in its cloud. Commands, file changes, builds, and repository data stay in the Daytona sandbox.
| Daytona class | Guest OS | Default snapshot source | Workspace |
|---|---|---|---|
container |
Linux | The included Dockerfile | /home/daytona/workspace |
linux-vm |
Linux | daytona-vm-medium |
/home/daytona/workspace |
windows |
Windows | windows-medium |
C:\cursor\workspace |
The controller commands below need a POSIX shell. The Windows support is for the Daytona guest, not for the controller computer.
- A macOS or Linux computer that stays online while workers run.
- Python 3.12 or newer, with the
venvmodule. - A POSIX shell and
curl. - A Daytona account and Daytona API key.
- A Daytona target that supports the selected sandbox class.
- Daytona quota for one snapshot build and one sandbox for each active worker. See Daytona limits.
- A Cursor Enterprise team and a Cursor service-account API key.
- A team-level Cursor GitHub App installation with access to each requested repository.
- Cursor team-administrator access to Cloud Agents settings.
Pool workers require an Enterprise service-account key. A personal Cursor API key cannot start a pool worker.
The linux-vm and windows builders also need their source snapshots in the selected Daytona target. You can select a different source with --source-snapshot.
Install the lab channel of the Cursor CLI on the computer that runs the controller. The stable channel does not include agent worker controller.
curl 'https://cursor.com/install?channel=lab' -fsS | bash
export PATH="$HOME/.local/bin:$PATH"If the Cursor CLI is already installed, select the lab channel and install its current release:
agent set-channel lab
agent updateVerify the installed release and the controller command:
agent --version
agent worker controller --helpagent --version must report 2026.09.02-e3e9343. agent worker controller --help must list --spawn.
Install this package from a clean daytona-guides clone:
cd python/cursor/self-hosted-machines
python3 -m venv .venv
. .venv/bin/activate
python3 -m pip install --editable .
command -v spawn-cursor-self-hosted-worker
command -v build-cursor-self-hosted-snapshotBoth commands must resolve inside the current .venv.
A Cursor team administrator must complete these steps:
- Open Dashboard > Cloud Agents > Self-Hosted.
- Enable Allow Self-Hosted Agents.
- Enable Self-Hosted Pools for the team.
- Enable GitHub token minting for self-hosted pool workers.
- Give the Cursor GitHub App access to each requested repository.
- Create one pool for each sandbox class that you plan to run.
For example, use daytona-container, daytona-linux-vm, and daytona-windows. See Cursor Self-Hosted Pools for current team settings and limits.
The spawn command configures the request's repository as the workspace origin before the worker starts, and the worker starts with --mint-github-token --on-session-start <checkout hook>. When Cursor claims the worker, it mints a short-lived GitHub token for the run and runs the checkout hook (checkout_repo.sh on Linux, checkout_repo.ps1 on Windows), which fetches the requested ref into the workspace.
Do not put a GitHub personal access token in .env.
Create the local environment file:
cp .env.example .envSet the keys and the Daytona target. Leave SNAPSHOT_NAME empty before the first build:
DAYTONA_API_KEY=replace-with-your-daytona-api-key
DAYTONA_TARGET=us
SNAPSHOT_NAME=
CURSOR_API_KEY=replace-with-your-cursor-service-account-keyUse a target that supports the class that you will build. Do not add CURSOR_AGENT_WORKER_ID, CURSOR_POOL, or CURSOR_REQUEST_ID. Cursor supplies these claim values.
The builder creates or reuses a content-addressed snapshot. It stores no API keys. Select one class and target:
# Linux container
build-cursor-self-hosted-snapshot --sandbox-class container --target us
# Linux VM
build-cursor-self-hosted-snapshot \
--sandbox-class linux-vm \
--target eu-central-1
# Windows
build-cursor-self-hosted-snapshot --sandbox-class windows --target usThe targets above are examples. Class availability depends on the Daytona organization.
The container build uses cursor_self_hosted/Dockerfile. A VM build creates a temporary sandbox from the source snapshot, provisions it inside the guest, stops it, and captures a cold snapshot. Every VM build or reuse then boots a fresh verifier. A failed verifier causes deletion of the uncertified snapshot.
The command prints JSON:
{"reused":false,"sandbox_class":"windows","snapshot_name":"cursor-self-hosted-windows-1234abcd","state":"active","target":"us"}Copy the exact snapshot_name into .env. The name above is only an example. Keep DAYTONA_TARGET set to the target that owns the snapshot. See Daytona snapshots.
Use --name only when you need a stable explicit name. The default name changes when any pinned recipe input changes. Use --source-snapshot to replace daytona-vm-medium or windows-medium.
Load the environment and start one controller for the selected snapshot:
set -a; . ./.env; set +a
agent worker controller \
--spawn "$(pwd)/.venv/bin/spawn-cursor-self-hosted-worker" \
--pool daytona-windowsKeep this foreground process running. Do not run spawn-cursor-self-hosted-worker directly during normal operation.
One controller configuration selects one snapshot and one Daytona target. Run a separate controller, environment, snapshot, and Cursor pool for each sandbox class. The spawn helper reads the class from the snapshot metadata. Do not set a separate sandbox-class environment variable.
- Open Cursor Agents.
- Create an agent for an HTTPS GitHub repository.
- Select the pool for the required Daytona class.
- Submit the request while that pool's controller runs.
The minted GitHub token does not authenticate an SSH remote.
- A user submits a request to a Cursor pool.
- The controller claims the request and calls the installed spawn command.
- The spawn command reads the snapshot class and creates a fresh sandbox from
SNAPSHOT_NAME. - The spawn command sets the request's repository as the workspace
origin. A Linux sandbox starts/usr/local/bin/agent. A Windows sandbox starts the pinnednode.exeand Cursor agent entry point. - Cursor mints a GitHub token for the run and runs the checkout hook, which fetches the requested ref into the class workspace.
- The controller computer starts a monitor. Linux monitoring reads
/proc. Windows monitoring checks the exact Node process path. - The monitor deletes the sandbox after the worker exits.
- If startup fails, the spawn command releases the claim, deletes the sandbox, and reports a redacted error.
If the monitor fails, Daytona auto-stop and delete-on-stop provide a fallback.
cursor_self_hosted/build_snapshot.pyselects the class builder.cursor_self_hosted/build_linux_vm_snapshot.pyprovisions, captures, and verifies Linux VM snapshots.cursor_self_hosted/build_windows_snapshot.pyprovisions, captures, and verifies Windows snapshots.cursor_self_hosted/config.pyparses controller values and builds worker commands and labels.cursor_self_hosted/spawn.pycreates the sandbox, launches the worker, and starts cleanup.cursor_self_hosted/worker_windows.pystarts and inspects the native Windows worker.cursor_self_hosted/monitor.pydeletes the sandbox when its worker exits.
| Variable | Required | Purpose |
|---|---|---|
DAYTONA_API_KEY |
Yes | Creates, finds, and deletes Daytona sandboxes. |
DAYTONA_TARGET |
Recommended | Selects the Daytona target that owns the snapshot. |
SNAPSHOT_NAME |
Yes after the build | Selects the active snapshot from the builder output. |
CURSOR_API_KEY |
Yes | Authenticates the controller and worker. Use an Enterprise service-account key. |
CURSOR_WORKER_IDLE_RELEASE_TIMEOUT |
No | Keeps a completed worker available for follow-up work. Default: 900 seconds. |
MONITOR_POLL_SECONDS |
No | Sets the monitor interval. Default: 5 seconds. |
SANDBOX_CREATE_TIMEOUT_SECONDS |
No | Sets the Daytona create and delete timeout. Default: 120 seconds. |
SANDBOX_LAUNCH_TIMEOUT_SECONDS |
No | Sets the worker launch timeout. Default: 60 seconds. |
No inbound port is required. The worker management address 0.0.0.0:8080 stays inside the sandbox.
| Runs from | Outbound HTTPS destination |
|---|---|
| Controller computer | cursor.com, downloads.cursor.com, the configured Python package index, api.cursor.com, and https://app.daytona.io/api |
| Daytona sandbox | api2.cursor.sh or api2direct.cursor.sh |
| Daytona sandbox | cloud-agent-artifacts.s3.us-east-1.amazonaws.com for optional artifact uploads |
| Daytona sandbox | The requested HTTPS Git host and task-specific package or tool hosts |
The controller computer holds both API keys. The sandbox receives the Cursor service-account key, but not the Daytona key. On Windows, the launcher removes its transient environment file after the worker starts.
Cursor requires the key in the worker process. Agent tools and repository code run as the same OS user. Untrusted code can inspect same-user process state and obtain the Cursor key.
Use a dedicated, least-privilege service account for each customer. Rotate the key after suspected exposure. Do not reuse sandboxes, service accounts, or Cursor keys across customers.
Cursor sends short-lived GitHub credentials to the claimed worker. This guide stores no GitHub token. Cursor receives file chunks for inference and uploaded artifacts. Review Cursor's security and network model.
The checkout hook fetches with that short-lived token only. Nothing stores a long-lived credential in the sandbox.
Run the deterministic checks:
.venv/bin/python -m pytest -q
.venv/bin/python tests/live_e2e.py --helpThe live verifier spends real Daytona and Cursor credits. It creates an agent and sandbox, asks Cursor to write an exact marker, checks the native worker process, stops that process, verifies monitor cleanup, and deletes the Cursor agent. In team-pool mode it also confirms that the requested repository is checked out in the workspace.
The default machine mode needs a personal Cursor user key. It uses Cursor My Machines so that any Cloud Agents account can verify the snapshot:
set -a; . ./.env; set +a
.venv/bin/python tests/live_e2e.py \
--sandbox-class linux-vm \
--target eu-central-1 \
--snapshot "$SNAPSHOT_NAME"Personal My Machines proves real Cursor execution in the selected snapshot. It does not prove the Enterprise pool claim path.
Use an Enterprise service-account key to test the complete controller and claim path. Pass a repository that the team's Cursor GitHub App can access:
.venv/bin/python tests/live_e2e.py \
--sandbox-class windows \
--target us \
--snapshot "$SNAPSHOT_NAME" \
--cursor-mode team-pool \
--repo-url https://github.com/your-org/your-repoRun the live command once for each class and its matching target.
agentis not found or has the wrong version. ExportPATHagain. Select the lab channel and runagent update. Use2026.09.02-e3e9343on the controller and in every snapshot.- The controller rejects the Cursor key. Confirm
CURSOR_API_KEYis an Enterprise service-account key. Load.envagain. - The spawn command reports missing claim values. Start it through
agent worker controller. Do not set claim values by hand. - The request stays queued. Confirm the controller and request use the same pool. Confirm Allow Self-Hosted Agents is enabled.
- Daytona cannot find the snapshot. Confirm
DAYTONA_TARGETmatches the target used for the build. - A VM build cannot find its source snapshot. Select a source in the same target with
--source-snapshot. - The workspace is empty after a Linux run. Inspect
/tmp/cursor-self-hosted/checkout.login the sandbox. Confirm GitHub token minting is enabled for the team and the Cursor GitHub App can access the repository. - The workspace is empty after a Windows run. Inspect
C:\ProgramData\cursor-self-hosted\checkout.log. Confirm GitHub token minting is enabled and the Cursor GitHub App can access the repository. - A sandbox remains after worker exit. Confirm the controller computer retained Daytona access. Delete the sandbox only when no worker uses it.
- A failed startup leaves a claimed request. Use the release-claim endpoint. Release only the failed request ID.