Use your own cloud

Run sandboxes and Spaces in your own AWS, Google Cloud, Azure, Hetzner or Modal account. Connect it, create on it, what Cua creates there, costs and cleanup.

Sandboxes and Spaces can run in a cloud account you own: --on aws (EC2), --on gcp (Compute Engine), --on azure (Azure Virtual Machines), --on hetzner (Hetzner Cloud servers) or --on modal (Modal Sandboxes). The same images, SDK calls and cua-bench runs work there, and you pay the cloud directly.

Each sandbox dials out to the cua.ai relay as a machine of your account, so nothing in your cloud listens on the internet. Your laptop, your agents and the Cua Spaces app reach it wherever they are. Streams, files and presence pass through relay.cua.ai, and only your machines and the accounts you share a Space with can reach the sandbox. The guest holds only its own machine token, never your account credentials. An account holds at most 32 machines, and each cloud sandbox uses one until you delete it.

Everything here creates resources that your cloud account bills for. Cua gives each one a time limit (8 hours by default) and cleans up after itself, but set a budget in your cloud first.

What you need#

  • The cloud's own CLI, signed in. Cua uses its credentials where they are and stores only the names:
    • AWS: a profile in ~/.aws (access keys, SSO or an assumed role).
    • Google Cloud: gcloud auth login and a project with the Compute Engine API enabled.
    • Hetzner: a project API token with Read & Write access. Hetzner has no sign-in, so Cua seals the token to your machines (see Hetzner and Modal tokens).
    • Modal: a profile in ~/.modal.toml (modal token new), or its token sealed to your machines.
    • Azure: nothing on this machine. Azure is always linked through Cua's sign-in, with no key. See Link a cloud without a key.
  • A cua.ai sign-in (cua auth login). Each cloud sandbox joins the relay as a machine of your account.

AWS and Google Cloud can also be linked with no key at all, through Cua's sign-in. See Link AWS without a key and Link a cloud without a key.

Connect a cloud#

cua cloud status shows each cloud, whether its credentials were found on this machine, and what it can run at what hourly cost:

cua cloud status

cua cloud test checks an account without creating anything. It checks the credentials, runs a dry run of a create, and reads the quota, the region and the machine types:

cua cloud test aws --profile default --region us-west-2

cua cloud connect runs the same checks and connects only when they pass. --default also makes the cloud the default location, so cua sb create and create_space go there when you give no location:

cua cloud connect aws --region us-west-2 --default
cua cloud connect gcp --project my-project --region us-central1
cua cloud connect modal --environment sandboxes
cua cloud connect hetzner --region fsn1

--ttl-hours sets how long each sandbox there lives before it deletes itself (default 8; 0 keeps it until you delete it; Modal caps a sandbox at 24; a Hetzner server shuts itself down then, and cua cloud sweep --delete removes it):

cua cloud connect aws --ttl-hours 2

The Cua Spaces app does the same from New Space, Your cloud, Connect a cloud. Hetzner asks for the API token there.

Limits for one cloud#

--budget sets the most Cua may spend in that cloud per calendar month (US dollars), and --max-size the largest machine (vCPUs x GB). Once the budget is reached, new machines there are refused before any cloud call; stopping and deleting always work. A larger machine is refused the same way, and the elastic pool never picks one. These limits come on top of the pool's own budget (cua cloud budget):

cua cloud connect aws --region us-west-2 --budget 200 --max-size 4x16

cua cloud status shows each cloud's budget, what was spent this month and how many machines run there.

With --oidc, Cua stores no AWS credentials, on this machine or anywhere else. Cua's issuer (oidc.cua.ai) signs a 15-minute token for your Cua account each time a machine needs AWS, and your AWS account trusts those tokens through one CloudFormation stack, cua-spaces. Every signed-in machine, and every agent on those machines, can then use the link, so you link the account once:

cua cloud connect aws --oidc --region us-west-2 --budget 200 --max-size 4x16

The first run returns needs-trust and a link to AWS CloudShell with one command to paste. That command creates the stack: an OIDC provider for your Cua account's issuer and the cua-spaces role. The role has the least-privilege policy plus a read of Cost Explorer. Then run the command again with the account id and --check. It gets a check token, exchanges it with AWS STS and dry-runs a machine:

cua cloud connect aws --oidc --region us-west-2 --budget 200 --max-size 4x16 --account-id 123456789012 --check

Every run ends with a status, the next step and the command that verifies it, so an agent can do this for you. See Connect a cloud (for agents). When an agent makes the link or raises a limit, a person approves it in the Cua Spaces app first. cua cloud disconnect aws stops new tokens at once. To remove the trust as well, delete the cua-spaces stack.

Google Cloud and Azure without a key#

Google Cloud (--oidc) and Azure (always) link the same way, through workload identity federation. The first run gives a Cloud Shell link or a Deploy to Azure link that sets up the trust. See Link a cloud without a key.

cua cloud connect gcp --oidc --project my-project --region us-central1 --budget 200 --max-size 4x16
cua cloud connect azure --region westus2 --budget 200 --max-size 4x16

Already connected?#

Clouds you linked with an earlier cua keep working after an update, with no step to take: the profile, project or environment names, the budget, the machines Cua created and their cleanup all carry over. An AWS link through the AWS CLI can switch to --oidc when you choose; until the check passes, the old link stays in force, and its region and limits carry over. If the cua daemon that runs is older than the cua you call it from, a link with --oidc or a budget is refused with a message to update and restart the daemon, instead of falling back to the CLI sign-in.

Hetzner and Modal tokens, sealed to your machines#

Hetzner and Modal have no OIDC, so they need an API token. Cua seals it to your machines, and you give it once for all of them:

cua cloud connect hetzner --token-stdin --region fsn1 --budget 50 --max-size 4x8
cua cloud connect modal --token-stdin --budget 50

Cua checks the token first and creates nothing. Your machine encrypts it with your account's pool key, so Cua's servers keep only ciphertext, and any of your machines opens it in memory when it calls the cloud. cua cloud status hetzner lists the devices that can open it. On another device, cua cloud connect hetzner --sealed --budget 50 links the same token without pasting it. --no-sealed keeps the Hetzner token on this machine only. Replacing, revoking and removed machines are covered in Connect a cloud.

Create sandboxes and Spaces#

A sandbox:

cua sb create linux --on aws --name research
cua sb exec aws:research -- uname -a
cua sb delete aws:research

From Python, including cua-bench runs:

from cua_sandbox import Image, Sandbox
 
async with Sandbox.ephemeral(Image.linux(), on="aws") as sb:
    print(await sb.shell.run("uname -a"))
    await sb.screenshot()
cb run cua-bench-basic --on aws

A Space is a cloud sandbox shown as the relay machine it joined as, relay:<machine>. Stop and start keep its disk (on AWS and Google Cloud):

cua spaces create linux --on aws --name research
cua spaces stop relay:cloud-0123456789abcdef
cua spaces start relay:cloud-0123456789abcdef
cua spaces delete relay:cloud-0123456789abcdef --force

On Hetzner, --on hetzner takes container images only, and stopping a Space does not stop the charge (see Notes for each cloud).

Agents use the same create_space tool with on="aws", and cloud_status, cloud_test, cloud_connect, cloud_disconnect, cloud_sweep and cloud_audit from the Spaces contract.

What Cua creates#

CloudPer sandboxShared
AWSOne EC2 instance and its disk. Docker runs the image on it.One security group with no inbound rules, in the region's default VPC.
Google CloudOne Compute Engine instance and its disk. Docker runs the image on it.A network with no inbound rules, so the project's default firewall rules never apply to it.
AzureOne VM (Ubuntu 24.04, no SSH key, no managed identity) with its disk, network interface and public IPv4 address, in the connection's resource group. Docker runs the image on it.A virtual network and a network security group, cua-sb-<owner>, with no rules of their own: Azure's defaults deny inbound traffic from the internet.
HetznerOne server with an IPv4 address and Docker (Ubuntu 24.04), and no SSH key. Docker runs the image on it.One firewall, cua-sb-<owner>, with no rules: Hetzner drops all inbound traffic. Your project's other firewalls are never used or changed.
ModalOne Modal sandbox running the image.Nothing.
  • Nothing else is created: no IAM users or keys, and no change to existing networks, security groups or firewall rules.

  • The VM installs only Docker. The image carries its own cua-spacesd.

  • Every resource carries these tags (labels on Google Cloud and Hetzner):

    TagValue
    cua-managedtrue
    cua-ownerThis cua home's owner id
    cua-spaceThe sandbox's name
    cua-machineIts relay machine
    cua-created-byThe cua version
    cua-expiresWhen it expires (Unix seconds)
  • Cua records every resource in ~/.cua/cloud/state.json before the cloud call, with its cloud id once it has one.

  • Every action is also appended to ~/.cua/cloud/audit.jsonl (see it with cua cloud audit): who recorded, created, confirmed or deleted what, and when. Names and ids only, never a secret.

Cua creates no IAM users, roles or keys, so it acts as whatever identity you connect (with --oidc, the cua-spaces role your own CloudFormation stack creates). Least privilege in your cloud has the AWS policy and Google Cloud permissions for a dedicated identity, and what Cua keeps when a machine does not come online.

Costs#

cua cloud status shows the estimated on-demand price of each image family per hour while it runs (compute and disk, not network). A stopped VM bills only its disk.

Cloudlinuxlinux-slim
AWSt4g.medium (arm64), about $0.04/hourt4g.small, about $0.03/hour
Google Cloude2-medium, about $0.04/houre2-small, about $0.03/hour
AzureStandard_B2s, about $0.05/hourStandard_B1ms, about $0.03/hour
Hetznercx23, about $0.0114/hourthe same
Modal1 core (2 vCPU) and 4 GiB, about $0.24/hourthe same
  • Hetzner: cx23 is $0.0104/hour plus $0.001/hour for the server's IPv4 address (checked 2026-10-22 against the Hetzner API). cua cloud status shows the live price of the type a create would pick. An Arm image uses cax11.
  • Windows and Omarchy are not offered yet. They run as VMs inside the cloud's VM, which needs a nested-virtualization machine type. cua cloud status says so next to them.
  • macOS is not offered. On AWS it would need an EC2 Mac dedicated host, which has a 24-hour minimum.

Notes for each cloud#

  • AWS:
    • One region per connection (--region, else the profile's). Profiles, SSO and assume-role profiles from ~/.aws work as the AWS CLI's do.
    • The instance allows only IMDSv2 with a hop limit of 1, so the sandbox's containers cannot read the instance's metadata (or its user data). It has no key pair and no instance profile.
    • A new account's vCPU quota may be low. cua cloud test aws does not read it yet, and a create past it fails with AWS's own message.
  • Google Cloud:
    • One project and zone per connection (--project, --region, --zone).
    • The instance has no service account, so nothing on it can call Google APIs. Its containers reach the metadata server only for DNS.
    • cua cloud test gcp says when the Compute Engine API is disabled.
  • Azure:
    • One subscription, resource group and region per connection. The roles reach only that group, and Cua creates nothing outside it.
    • The VM has a random password that is never stored or shown, and no SSH key; nothing can reach it from outside, since the network security group has no rules of its own. It has no managed identity, so nothing on it can call Azure.
    • Azure cannot end a VM at a deadline. The cua-expires tag and the sweep do (cua cloud sweep azure --delete, and the daemon every 15 minutes).
    • Stopping a Space deallocates the VM, so only its disk and address are billed. A shutdown from inside the VM keeps it allocated and billed.
    • The OS disk is created by Azure from the VM and is not tagged; it is deleted with the VM.
  • Hetzner:
    • One location per connection (--region, default fsn1; also nbg1, hel1, ash, hil, sin). Cua picks the cheapest shared-vCPU type of the location that fits the image.
    • Create a project API token in the Hetzner Cloud Console under Security, API tokens, with Read & Write access. A read-only token is refused.
    • Give the token to cua cloud connect hetzner on standard input (--token-stdin) or in HCLOUD_TOKEN; the hcloud CLI's active context also works. Only a token that passes the checks is used. It is sealed to your machines, or with --no-sealed saved in this machine's credential store (the system vault on a Cua-signed Mac, a private file in the cua home elsewhere), never in config.toml.
    • Container images only. Hetzner Cloud servers have no /dev/kvm, so VM images (Windows, Omarchy) are refused with that reason.
    • Primary IPv4 limit. Every server takes one primary IPv4 address, and a new project starts with 5, so it holds about 5 Cua servers. Servers get no IPv6 address, so it would not take a second one from the limit. A create past the limit fails with Hetzner's own message. Ask Hetzner to raise the limit, or delete servers.
    • A stopped server is still billed. Hetzner bills the reserved resources, so stop keeps the disk but does not stop the charge. Delete the Space to stop paying.
    • Hetzner cannot delete a server from inside it. The server shuts itself down at its time limit, and cua cloud sweep (or any later create) deletes it. Until then it is billed.
    • The API allows about 3600 requests an hour per project; Cua backs off when Hetzner answers 429.
  • Modal:
    • One environment per connection (--environment) and a ~/.modal.toml profile (--profile), or a token sealed to your machines (--token-stdin, given as <token id>:<token secret>).
    • Sandboxes run under gVisor by default. --runtime microvm uses Modal's VM runtime. Neither has /dev/kvm, so only Linux images run.
    • A sandbox cannot be stopped and started, and lives at most 24 hours.
    • Cua talks to Modal through cua-modal-helper, which uses Modal's official Go SDK. Build it with go build in libs/cua/crates/cua-contrib/modal-helper, and put it next to cua, on PATH, or in CUA_MODAL_HELPER.

Time limits and cleanup#

Four things end a sandbox that nobody deleted:

  • It shuts itself down. A VM runs shutdown at its time limit and checks it again after every reboot or start. Shutting down deletes it (AWS terminates on shutdown; Google Cloud's run limit deletes it). On Hetzner it only powers the server off (still billed) until a sweep deletes it. Modal ends a sandbox at its timeout.
  • A failed create cleans up. Whatever it made is deleted, and its relay machine is removed.
  • cua cloud sweep lists expired sandboxes and anything Cua created whose sandbox is gone. With --delete it deletes them. --all includes every Cua resource in that cloud.
  • The daemon sweeps for you. While the Cua daemon runs, it sweeps every connected cloud with --delete (not --all) every 15 minutes (CUA_CLOUD_SWEEP_SECS changes the interval; 0 turns it off). How it works: Cleanup, disconnect and audit in your cloud.
cua cloud sweep
cua cloud sweep aws --delete
cua cloud sweep --all --delete

The sweeper touches only a resource that the cloud shows with cua-managed=true and this home's cua-owner, and that this home recorded or that carries its owner id. It never matches by name. Untagged resources and other owners' resources are never deleted, whatever they are called.

cua cloud disconnect refuses while sandboxes Cua created still run in that cloud, so none is forgotten and left to bill (--delete removes them first), and cua cloud audit shows what Cua did in your clouds. Details: Cleanup, disconnect and audit in your cloud.

Details: Least privilege in your cloud and Cleanup, disconnect and audit. For the other places a sandbox runs, see Choose where sandboxes run.