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 loginand 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.
- AWS: a profile in
- 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 statuscua 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-2cua 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 2The 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 4x16cua cloud status shows each cloud's budget, what was spent this month and
how many machines run there.
Link AWS without a key#
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 4x16The 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 --checkEvery 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 4x16cua cloud connect azure --region westus2 --budget 200 --max-size 4x16Already 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 50Cua 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:researchFrom 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 awsA 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 --forceOn 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#
| Cloud | Per sandbox | Shared |
|---|---|---|
| AWS | One 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 Cloud | One 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. |
| Azure | One 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. |
| Hetzner | One 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. |
| Modal | One 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):
Tag Value cua-managedtruecua-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.jsonbefore the cloud call, with its cloud id once it has one. -
Every action is also appended to
~/.cua/cloud/audit.jsonl(see it withcua 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.
| Cloud | linux | linux-slim |
|---|---|---|
| AWS | t4g.medium (arm64), about $0.04/hour | t4g.small, about $0.03/hour |
| Google Cloud | e2-medium, about $0.04/hour | e2-small, about $0.03/hour |
| Azure | Standard_B2s, about $0.05/hour | Standard_B1ms, about $0.03/hour |
| Hetzner | cx23, about $0.0114/hour | the same |
| Modal | 1 core (2 vCPU) and 4 GiB, about $0.24/hour | the same |
- Hetzner:
cx23is $0.0104/hour plus $0.001/hour for the server's IPv4 address (checked 2026-10-22 against the Hetzner API).cua cloud statusshows the live price of the type a create would pick. An Arm image usescax11. - 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 statussays 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~/.awswork 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 awsdoes not read it yet, and a create past it fails with AWS's own message.
- One region per connection (
- 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 gcpsays when the Compute Engine API is disabled.
- One project and zone per connection (
- 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-expirestag 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, defaultfsn1; alsonbg1,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 hetzneron standard input (--token-stdin) or inHCLOUD_TOKEN; thehcloudCLI's active context also works. Only a token that passes the checks is used. It is sealed to your machines, or with--no-sealedsaved in this machine's credential store (the system vault on a Cua-signed Mac, a private file in the cua home elsewhere), never inconfig.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
stopkeeps 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.
- One location per connection (
- Modal:
- One environment per connection (
--environment) and a~/.modal.tomlprofile (--profile), or a token sealed to your machines (--token-stdin, given as<token id>:<token secret>). - Sandboxes run under gVisor by default.
--runtime microvmuses 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 withgo buildinlibs/cua/crates/cua-contrib/modal-helper, and put it next tocua, onPATH, or inCUA_MODAL_HELPER.
- One environment per connection (
Time limits and cleanup#
Four things end a sandbox that nobody deleted:
- It shuts itself down. A VM runs
shutdownat 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 sweeplists expired sandboxes and anything Cua created whose sandbox is gone. With--deleteit deletes them.--allincludes 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_SECSchanges the interval;0turns it off). How it works: Cleanup, disconnect and audit in your cloud.
cua cloud sweep
cua cloud sweep aws --delete
cua cloud sweep --all --deleteThe 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.