#Template

A Template is the blueprint for creating Sandboxes. It defines the container image, resource limits, warm pool size, and default network policy. Every Sandbox is created from a Template.

Creating, updating, and deleting team-owned templates requires team admin permissions. developer and builder roles can read templates, and builder is typically used for registry push workflows rather than template management.

Template Types#

Sandbox0 has two categories of templates:

TypeDescriptionVisibility
BuiltinSystem-provided templates managed by the platform operatorPublic — visible to all teams
CustomTemplates created and owned by your teamPrivate — visible to your team only (configurable)

Builtin templates use curated public images and are ready to use immediately. Custom templates let you bring your own image, fine-tune resources, mount volumes, configure environment variables, and define default network policy.


Choose A Creation Method#

Sandbox0 supports three ways to reuse an environment:

NeedPrefer
A reproducible, source-controlled image build or a different base imageCreate a custom template from an image
A reusable template ID backed by the current root filesystem of an initialized sandboxCreate a custom template from a sandbox
Fast reuse of initialized rootfs state with the same existing templateRootfs snapshot and claim with snapshot_id
Data that must outlive sandbox identity, be mounted by multiple sandboxes, or be accessed through direct storage APIsSandbox Volume

Creating a template from a sandbox captures its current writable root filesystem, publishes an OCI image to the registry configured for the team, and changes the template to use a digest-pinned image. This is useful when initialization is interactive or agent-driven and you want the result to become a normal template that can be listed, shared within the team, reconciled across clusters, and assigned its own warm pool.

You do not need a new template ID for every initialized environment. If the base image, default resources, network policy, and mount declarations already fit, create a rootfs snapshot and pass its snapshot_id when claiming future sandboxes:

  1. claim a sandbox from a platform-owned builtin template such as default
  2. install packages, populate caches, clone repositories, or write tool configuration
  3. pause the sandbox and wait for status to become paused
  4. create a named rootfs snapshot for the initialized state
  5. claim each task sandbox with the same template and snapshot_id

For platform-owned builtin templates, the operator manages the warm pool. That lets teams reuse warm builtin capacity and store only the initialized rootfs state for their own dependency set instead of maintaining a separate custom template pool for each variation.

Rootfs snapshots do not create template IDs and do not appear in s0 template list. Claiming with snapshot_id creates a new running sandbox from the selected rootfs snapshot while the requested template still controls image, default resources, mounts, services, and network defaults. A claim or runtime update can override only the sandbox memory limit; CPU is derived from the platform memory-per-CPU ratio with a 150m minimum limit. Snapshot and fork accept a running or paused source sandbox. Restore remains useful for rolling back an existing paused sandbox. See Snapshot And Restore for the lifecycle requirements and API workflow, or read Initialize Once, Claim Many for the custom-rootfs pattern.


Create Template#

Create a custom template for your team. The template spec is defined in a YAML file.

CLI examples assume you already ran s0 auth login and selected the correct current team.

POST

/api/v1/templates

Request Body#

FieldTypeDescription
template_idstringUnique identifier for the template (e.g., my-python-env)
specobjectFull template specification

Template IDs must be unique within your team. Once created, the ID cannot be changed — create a new template if you need a different ID.

Example spec file (template.yaml):

yaml
spec: mainContainer: image: python:3.12-slim resources: memory: 4Gi ephemeralStorage: 8Gi pool: minIdle: 2 maxIdle: 10
go
tpl, err := client.CreateTemplate(ctx, apispec.TemplateCreateRequest{ TemplateID: "my-python-env", Spec: apispec.SandboxTemplateSpec{ MainContainer: apispec.NewOptContainerSpec(apispec.ContainerSpec{ Image: "python:3.12-slim", Resources: apispec.ResourceQuota{ Memory: "4Gi", EphemeralStorage: apispec.NewOptString("8Gi"), }, }), Pool: apispec.NewOptPoolStrategy(apispec.PoolStrategy{ MinIdle: 2, MaxIdle: 10, }), }, }) if err != nil { log.Fatal(err) } fmt.Printf("Template created: %s\n", tpl.TemplateID)

Create Template From A Sandbox#

Create a custom template from the current root filesystem of an existing sandbox. Sandbox0 captures and publishes the image asynchronously, so the endpoint returns 202 Accepted with a template whose status.creation.state is creating.

POST

/api/v1/templates/from-sandbox

Request Body#

FieldTypeDescription
template_idstringUnique ID for the new team-owned template
sandbox_idstringSource sandbox whose current root filesystem will be captured
spec_overridesobjectOptional safe overrides: description, displayName, tags, and pool

The new template inherits safe settings from the source sandbox's template. Sandbox0 does not copy sandbox identity, active processes, memory contents, sessions, claim-time environment or network overrides, attached Sandbox Volumes, services, or privileged pod and security fields. If pool is omitted, it defaults to minIdle: 0 and maxIdle: 0.

The source sandbox may be running or paused. Because capture is asynchronous, request acceptance is not the rootfs capture point. Keep the source sandbox available and avoid rootfs writes while the template is in the capturing stage. Sandbox0 briefly barriers and checkpoints a running source, then records the exact capture point in status.creation.capturedAt; after that, the source can continue changing or be deleted without changing the image build.

You do not provide an image repository or registry credentials. Sandbox0 publishes to the registry configured by the platform operator and stores a digest-pinned image reference in the completed template.

The configured registry pull address must be reachable by every sandbox node. For the built-in registry, operators should set spec.registry.builtin.pushEndpoint or Ingress to a node-reachable address. Sandbox0 uses the Kubernetes Service address only for manager's internal publication path.

The source template image must remain resolvable by digest from its original registry while the build is publishing. A node-local or preloaded image with no matching registry manifest cannot currently be used as the source; publish that base image to a registry and update the source template first.

The caller needs both template:create and sandbox:read permissions.

To retry safely after a network error, send the same Idempotency-Key header with the same request body. The key remains bound to that create request while the template exists, even if the ready template is later updated; a replay returns the template's current representation. Reusing the key with a different request returns a conflict. Deleting the template releases the key.

Optional overrides file (template-overrides.yaml):

yaml
displayName: Python workspace description: Preinstalled Python dependencies and repository checkout tags: - python - initialized pool: minIdle: 1 maxIdle: 3
go
req := sandbox0.NewTemplateFromSandboxCreateRequest( "python-workspace", sourceSandbox.ID, &apispec.TemplateFromSandboxSpecOverrides{ DisplayName: apispec.NewOptString("Python workspace"), Pool: apispec.NewOptPoolStrategy(apispec.PoolStrategy{ MinIdle: 1, MaxIdle: 3, }), }, ) tpl, err := client.CreateTemplateFromSandbox(ctx, req, &sandbox0.CreateTemplateFromSandboxOptions{ IdempotencyKey: "python-workspace-v1", }) if err != nil { log.Fatal(err) } tpl, err = client.WaitTemplateReady(ctx, tpl.TemplateID, nil) if err != nil { log.Fatal(err) } fmt.Printf("Template ready: %s\n", tpl.TemplateID)

Creation progresses through these stages:

StateStageMeaning
creatingcapturingSandbox0 is taking a point-in-time rootfs snapshot
creatingpublishingThe OCI image is being assembled and pushed
creatingreconcilingThe digest-pinned template is being distributed to data-plane clusters
readyreconcilingThe template is visible in a data-plane cluster and the claim API accepts it
failedLast active stageCreation stopped; inspect reason and message

GET /api/v1/templates/{'{id}'} and template list responses include templates in all three states. A traditional image-based template has no status.creation object and is ready immediately. While a template is creating or failed, it cannot be updated or used to claim a sandbox. It can still be deleted; deleting it also cancels any unfinished build work.

With a zero-sized pool, ready does not mean the image has already been pulled into a sandbox node. The first claim still performs the normal image pull and sandbox startup.

Deleting or cancelling a template releases Sandbox0's internal rootfs snapshot pin and build records. It does not synchronously delete OCI tags or blobs that may already have reached the configured registry. Operators should apply an appropriate retention or lifecycle policy to Sandbox0's team-scoped template repositories.

SDK wait helpers only stop client-side polling when their context, abort signal, timeout, or caller interruption ends. They do not cancel the server-side build.


Get Template#

Retrieve a specific template by ID. Your team can access both its own templates and builtin templates.

GET

/api/v1/templates/{id}

go
tpl, err = client.GetTemplate(ctx, "my-python-env") if err != nil { log.Fatal(err) } fmt.Printf("Template: %s (scope: %s)\n", tpl.TemplateID, tpl.Scope)

List Templates#

List all templates visible to your team — including your team's custom templates and all public builtin templates.

GET

/api/v1/templates

go
templates, err := client.ListTemplate(ctx) if err != nil { log.Fatal(err) } for _, tpl := range templates { display, _ := tpl.Spec.DisplayName.Get() fmt.Printf("- %s (%s) scope=%s\n", tpl.TemplateID, display, tpl.Scope) }

Update Template#

Update the spec of an existing custom template. The update triggers a reconciliation across all clusters — running sandboxes are not affected.

PUT

/api/v1/templates/{id}

Updating a template does not affect already-running Sandboxes. New Sandboxes claimed after the update will use the new spec. Idle pods in the pre-warm pool are recycled and replaced with pods matching the new spec.

go
tpl, err = client.UpdateTemplate(ctx, "my-python-env", apispec.TemplateUpdateRequest{ Spec: apispec.SandboxTemplateSpec{ MainContainer: apispec.NewOptContainerSpec(apispec.ContainerSpec{ Image: "python:3.12-slim", Resources: apispec.ResourceQuota{ Memory: "8Gi", EphemeralStorage: apispec.NewOptString("8Gi"), }, }), Pool: apispec.NewOptPoolStrategy(apispec.PoolStrategy{ MinIdle: 3, MaxIdle: 10, }), }, }) if err != nil { log.Fatal(err) } fmt.Printf("Template updated: %s\n", tpl.TemplateID)

Delete Template#

Delete a custom template. All cluster allocations for the template are cleaned up via reconciliation.

DELETE

/api/v1/templates/{id}

Deleting a template does not terminate running Sandboxes created from it, but no new Sandboxes can be created from the deleted template.

go
_, err = client.DeleteTemplate(ctx, "my-python-env") if err != nil { log.Fatal(err) } fmt.Println("Template deleted")

Next Steps#

Custom Images

Build and reference custom container images for sandbox templates.

Warm Pool

Use warm pools to reduce startup latency for common templates.

Snapshot And Restore

Reuse initialized rootfs state without creating another template.

Initialize Once, Claim Many

Customize a builtin template with rootfs snapshots and claim-time snapshot IDs.