Image
The Image builder, sidecar containers and registry credentials of cua-sandbox.
The Image builder, sidecar containers and registry credentials of cua-sandbox.
Image is an immutable, chainable description of what a sandbox boots. Builder methods return a new Image.
Builder methods add layers and return a new Image. Locally, a container image is built into your container engine before boot; in the cloud the same layers run as a remote build. Both are cached by content. Layers a build cannot run (brew_install, choco_install, ...) are applied after boot through cua-spacesd, so they need an image that runs it.
class Image
Image(os_type: str, distro: str, version: str, kind: Optional[str] = None)Immutable, chainable image specification.
Each mutation method returns a new Image instance so that builders can be forked at any point.
| Attribute | Type | Description |
|---|---|---|
os_type | str | |
distro | str | |
version | str | |
kind | Optional[str] |
Example
from cua_sandbox import Image
img = Image.linux().apt_install("curl").pip_install("requests").env(DEBUG="1").expose(8080)
print(img.to_dict())
# {'os_type': 'linux', 'distro': 'ubuntu', 'version': '24.04', 'kind': None,
# 'layers': [{'type': 'apt_install', 'packages': ['curl']},
# {'type': 'pip_install', 'packages': ['requests']}],
# 'env': {'DEBUG': '1'}, 'ports': [8080]}@classmethod
def linux(
distro: str = 'ubuntu',
version: str = '24.04',
kind: Optional[str] = None,
tier: Optional[str] = None,
) -> ImageLinux: the canonical ghcr.io/trycua/linux:<version> image.
kind=None (or "auto") runs what the image is (the rootfs: a
gVisor container locally and in the cloud); kind="vm" its
-disk containerDisk (QEMU locally, KubeVirt in the cloud);
kind="container" the rootfs. Sandbox.create(kind=...)
overrides it.
tier: "full" (the default: dev tooling) or "slim"
(24.04-slim: cua-spacesd and Chromium only; what CI runs). A tier
CI has not published yet raises CuaError.ImageNotPublished; use
Image.from_registry(ref) to run it anyway.
@classmethod
def macos(version: str = '26', kind: str = 'vm', tier: Optional[str] = None) -> ImagemacOS: the canonical ghcr.io/trycua/macos:<version> image (Lume locally).
Supported versions: "15" / "sequoia", "26" / "tahoe".
tier: "full" (the default), "slim" or "xcode" /
"xcode-<X.Y>" (full plus one pinned Xcode); unpublished tiers
raise CuaError.ImageNotPublished.
@classmethod
def windows(
version: str = '2022',
kind: str = 'vm',
tier: Optional[str] = None,
) -> ImageWindows: the canonical ghcr.io/trycua/windows:2022 containerDisk.
Other versions, including "11", have no canonical image: on Fleet
they are unsupported, and locally they are installed from a downloaded
evaluation ISO. tier="slim" names 2022-slim once published.
@classmethod
def omarchy(channel: Optional[str] = None) -> ImageOmarchy (Arch Linux, Hyprland) with cua-spacesd:
ghcr.io/trycua/omarchy:edge (or "rc"/"stable").
An amd64 VM: QEMU locally (emulated on arm64 hosts, so slow there),
KubeVirt in the cloud. Until CI publishes it this raises
CuaError.ImageNotPublished; use
Image.from_registry("ghcr.io/trycua/omarchy:edge", kind="vm") to
run it anyway.
@classmethod
def android(version: str = '14', kind: str = 'vm') -> ImageAndroid image. Always a VM (QEMU emulator).
@classmethod
def from_registry(
ref: str,
*,
os_type: str = 'linux',
kind: Optional[str] = None,
agent_type: Optional[str] = None,
secret: Optional[Any] = None,
) -> ImageCreate an image from a registry reference.
secret (a cua_sandbox.RegistrySecret) pulls a private
image, the same way locally (the pull) and in the cloud (a registry
pull secret for the sandbox). It is never logged or saved.
os_type selects the firmware: Windows guest disks are built UEFI-only,
so a Windows containerDisk pulled from a registry must say so or it is
handed BIOS and will not boot. kind is resolved after pull when omitted.
agent_type names the guest control server the disk already runs, the
same hint as from_file: "osworld" selects the OSWorld Flask
server on port 5000 instead of the computer-server on 8000, both for
local QEMU and for Fleet pools.
@classmethod
def from_file(
path: str,
*,
os_type: str = 'windows',
kind: str = 'vm',
agent_type: Optional[str] = None,
) -> ImageCreate an image from a local disk, ISO file, or URL.
Supported formats: qcow2, vhdx, raw, img, iso. URLs (http/https) are downloaded automatically. Zip files are extracted. For ISOs, the runtime will create a qcow2 disk and attach the ISO as a CD-ROM for installation/boot.
| Parameter | Type | Default | Description |
|---|---|---|---|
path | str | required | Local file path or URL (http/https). |
os_type | str | 'windows' | OS type hint ("linux", "windows", "macos", "android"). |
kind | str | 'vm' | "vm" or "container". |
agent_type | Optional[str] | None | Agent type hint (e.g. "osworld" for OSWorld Flask server). |
@classmethod
def from_dict(data: Dict[str, Any]) -> ImageReconstruct an Image from a serialized spec dict.
Example
from cua_sandbox import Image
base = Image.linux().apt_install("git")
dev = base.pip_install("rich")
same = Image.from_dict(dev.to_dict()).to_dict() == dev.to_dict()
print([same, len(base.to_dict()["layers"]), len(dev.to_dict()["layers"])])
# [True, 1, 2]def apt_install(*packages: str) -> ImageInstall packages via apt (Linux only).
def brew_install(*packages: str) -> ImageInstall packages via Homebrew (macOS).
def choco_install(*packages: str) -> ImageInstall packages via Chocolatey (Windows).
def winget_install(*packages: str) -> ImageInstall packages via winget (Windows).
def apk_install(*apk_paths: str) -> ImageInstall APK files via adb (Android only).
def pwa_install(
manifest_url: str,
package_name: Optional[str] = None,
keystore: Optional[str] = None,
keystore_alias: str = 'android',
keystore_password: str = 'android',
builder: str = 'pwa2apk',
push_timeout: Optional[float] = None,
) -> 'Image'Build an APK from a PWA manifest URL and install it (Android only).
Two builder backends are supported:
"pwa2apk" (default): generates a lightweight WebView-based APK.
No Chrome dependency, no "Running in Chrome" banner, no Digital Asset
Links needed. ~10 KB APK."bubblewrap": generates a Chrome Trusted Web Activity (TWA) APK.
Requires Chrome on the device and a matching
/.well-known/assetlinks.json on the server. Shows a mandatory
"Running in Chrome" privacy disclosure on every launch.| Parameter | Type | Default | Description |
|---|---|---|---|
manifest_url | str | required | Full URL to the PWA's manifest.json or manifest.webmanifest. Example: "http://10.0.2.2:3000/manifest.json" |
package_name | Optional[str] | None | Android package ID. Defaults to a reversed-hostname derivation (e.g. "com.example.app"). |
keystore | Optional[str] | None | Path to a *.keystore / *.jks file. When omitted a fresh keystore is generated and cached. Pass the keystore bundled with your PWA repo so the fingerprint is deterministic. |
keystore_alias | str | 'android' | Key alias inside the keystore (default "android"). |
keystore_password | str | 'android' | Password for both the store and the key (default "android"). |
builder | str | 'pwa2apk' | "pwa2apk" (default) or "bubblewrap". |
push_timeout | Optional[float] | None | Optional seconds for the write_bytes adb push of the built APK. When None the server default applies. |
def uv_install(*packages: str) -> ImageInstall Python packages via uv add into the cua-server project.
def pip_install(*packages: str) -> ImageInstall Python packages via pip.
def app_install(app_id: str) -> ImageInstall an app from the cua-sandbox-apps catalog.
Requires cua-sandbox-apps to be installed. The app's install
script for this image's OS is executed as a build layer.
def run(command: str) -> ImageRun a shell command during image build.
def env(**variables: str) -> ImageSet environment variables.
def copy(src: str, dst: str) -> ImageCopy a file into the image.
def expose(port: int) -> ImageExpose a port.
def to_build_recipe(
*,
name: str,
namespace: str,
tags: Mapping[str, str] | None = None,
timeout_seconds: int | None = None,
disk_size: str | None = None,
file_references: Mapping[str, ImageFileReference] | None = None,
) -> Dict[str, Any]Return a CRD-validated Image custom-resource manifest.
def to_dict() -> Dict[str, Any]Serialize to a plain dict suitable for JSON or cloud API.
def to_cloud_init() -> strGenerate a cloud-init user-data script from the image layers.
def local_support()Check whether this image can run locally on the current host.
Returns a cua_sandbox.runtime.compat.RuntimeSupport describing:
supported : runtime is available or auto-installable on this OShw_accel : hardware acceleration (HVF / KVM / Hyper-V) is availableruntime_installed: runtime binary found right now (no install needed)auto_installable: SDK can install the runtime automaticallyreason : human-readable explanationIn tests, use the bundled helper instead of checking manually:
from cua_sandbox.runtime.compat import skip_if_unsupported
async def test_something():
skip_if_unsupported(Image.macos())
async with Sandbox.ephemeral(Image.macos(), local=True) as sb:
...class ImageInfo
ImageInfo(
reference: str,
pinned_ref: str,
digest: str,
variant: str,
arch: Optional[str],
os: str,
emulated: bool,
)The image a sandbox runs, as resolved and pinned at create time.
reference is the reference as requested (normalised, e.g.
docker.io/library/python:3.12-slim), pinned_ref the
registry/repo@sha256:... that ran, variant one of rootfs,
containerdisk or lume, arch the architecture that
ran (amd64/arm64, when known), os the guest OS and
emulated whether it ran under emulation.
| Attribute | Type | Description |
|---|---|---|
reference | str | |
pinned_ref | str | |
digest | str | |
variant | str | |
arch | Optional[str] | |
os | str | |
emulated | bool |
class Container
Container(
image: str,
command: Optional[Sequence[str]] = None,
env: Optional[Mapping[str, str]] = None,
ports: Optional[Sequence[int]] = None,
name: Optional[str] = None,
)A sidecar container, reachable from the sandbox at its name.
name (a DNS label, not main) defaults from the image (redis
for redis:7-alpine); the sidecar reaches the sandbox at main.
command replaces the image's entrypoint. env holds plain values,
not secrets.
| Attribute | Type | Description |
|---|---|---|
image | str | |
command | Optional[Sequence[str]] | |
env | Optional[Mapping[str, str]] | |
ports | Optional[Sequence[int]] | |
name | Optional[str] |
def native() -> Anyclass RegistrySecret
RegistrySecret(
username: str,
password: str,
*,
registry: Optional[str] = None,
)Credentials for a private registry image.
RegistrySecret(username, password): explicit values (a token works
as the password).RegistrySecret.from_env(): read CUA_REGISTRY_USERNAME /
CUA_REGISTRY_PASSWORD (or the variables you name) at create time.RegistrySecret.aws_ecr(region=None): a private Amazon ECR image,
with a login token from the AWS CLI.registry scopes them to one host; by default they apply to the
image's registry. The password never appears in repr or logs.
| Attribute | Type | Description |
|---|---|---|
registry | Optional[str] | |
username | Optional[str] |
Example
from cua_sandbox import Image, RegistrySecret
print(Image.from_registry("python:3.12-slim").to_dict()["registry"])
print(repr(RegistrySecret("robot", "s3cr3t", registry="registry.example.com"))) # the password never shows@classmethod
def from_env(
username_var: str = 'CUA_REGISTRY_USERNAME',
password_var: str = 'CUA_REGISTRY_PASSWORD',
*,
registry: Optional[str] = None,
) -> 'RegistrySecret'@classmethod
def aws_ecr(region: Optional[str] = None) -> 'RegistrySecret'def native() -> Anyclass ImageFileReference(BaseModel)| Attribute | Type | Description |
|---|---|---|
model_config | ConfigDict | |
digest | constr(pattern='^sha256:[0-9a-f]{64}$') | |
reference | constr(pattern='^uploads/[a-z0-9]([-a-z0-9]*[a-z0-9])?/[A-Za-z0-9_-]+$', min_length=1, max_length=2048) | |
size_bytes | conint(ge=0, le=9007199254740991) |