Choose and build a sandbox image
Select a sandbox base image and add packages, files, setup commands, ports, and inspection.
Use an Image to define the OS and software environment for a sandbox before you start it.
Choose a base image
An Image describes the OS and software environment. Images are immutable and chainable. Each builder method returns a new Image.
Start with the built-in constructor that matches the operating system and isolation level you need.
from cua import Image
Image.linux() # Ubuntu 24.04 VM (default, QEMU)
Image.linux(kind='container') # Ubuntu 24.04 container (lighter, Docker/XFCE)
Image.linux('ubuntu', '22.04') # Older Ubuntu
Image.macos() # macOS Tahoe (version '26', latest)
Image.macos('15') # macOS Sequoia
Image.windows() # Windows 11
Image.android() # Android 14| Image | Kind | Notes |
|---|---|---|
| Image.linux() | VM | Default (QEMU), supports /dev/kvm |
| Image.linux(kind='container') | container | Lighter, Docker/XFCE |
| Image.macos() | VM | Apple Virtualization / Lume |
| Image.windows() | VM | QEMU or Hyper-V |
| Image.android() | VM | QEMU Android emulator |
Install packages
Use the package manager for the target operating system.
Image.linux().apt_install('curl', 'git', 'ffmpeg')
Image.macos().brew_install('ffmpeg', 'jq')
Image.windows().choco_install('nodejs', 'git')
Image.windows().winget_install('Microsoft.VisualStudioCode')
Image.android().apk_install('/path/to/app.apk')Install Python packages with pip_install() or uv_install().
Image.linux().pip_install('numpy', 'pandas', 'playwright')
Image.linux().uv_install('numpy', 'pandas') # faster, installs into cua-server projectSet environment variables
Set non-sensitive environment variables with .env().
Image.linux().env(DATABASE_URL='postgres://localhost/mydb', LOG_LEVEL='debug')Avoid putting real secrets in .env() because anyone who can read the Image spec can see the values.
Run setup commands
Run arbitrary shell commands during setup with .run().
Image.linux().run('curl -fsSL https://deb.nodesource.com/setup_20.x | bash -')Copy files
Copy local files into the image with .copy().
Image.linux().copy('./config.json', '/app/config.json')Expose ports
Expose every port the sandbox workload must listen on.
Image.linux().expose(8080).expose(5432)Chain builder calls
Chaining: builder calls are composable, each returns a new Image:
img = (
Image.linux()
.apt_install('curl', 'git', 'ffmpeg')
.pip_install('requests', 'Pillow')
.env(MY_TOKEN='abc123', DEBUG='1')
.run('mkdir -p /app/data')
.expose(8080)
)Fork a base image for different use cases:
base = Image.linux().apt_install('curl', 'git')
dev = base.pip_install('ipython', 'rich').env(DEBUG='1')
prod = base.pip_install('gunicorn').run('useradd -m appuser')Use a custom OCI image
Use Image.from_registry() when your base image already exists in a registry.
Image.from_registry('ghcr.io/trycua/macos-tahoe-cua:latest')
Image.from_registry('ubuntu:22.04')
# chain builder on top
img = Image.from_registry('ubuntu:22.04').apt_install('curl')Use a local disk image
Use Image.from_file() for qcow2, vhdx, raw, or iso disk images. URLs are also supported and cached automatically.
Image.from_file('/path/to/disk.qcow2', os_type='linux')
Image.from_file('/path/to/windows.vhdx', os_type='windows')
Image.from_file('https://example.com/disk.qcow2', os_type='linux')Images are cached in ~/.cua/cua-sandbox/image-cache/.
Inspect an image
Call .to_dict() to inspect the final image spec before using it.
img = Image.linux().apt_install('curl').pip_install('requests')
print(img.to_dict())
# {'os_type': 'linux', 'distro': 'ubuntu', 'version': '24.04', 'kind': 'container', 'layers': [...]}