# 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.

<Callout variant="info">
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.
</Callout>

## Template Types

Sandbox0 has two categories of templates:

| Type | Description | Visibility |
|------|-------------|------------|
| **Builtin** | System-provided templates managed by the platform operator | Public — visible to all teams |
| **Custom** | Templates created and owned by your team | Private — 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.

---

## Custom Template Or Rootfs Snapshot

You do not need a custom template for every initialized environment. If the base image, default resources, network policy, and mount declarations already fit, you can claim a sandbox from a builtin template, install dependencies or clone repositories inside the writable root filesystem, create a rootfs snapshot, then claim future sandboxes with that `snapshot_id`.

This is useful for dependency-heavy agent workspaces:

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.

| Need | Prefer |
|------|--------|
| Different base image, default resource envelope, default environment, mount paths, network defaults, or privileged runtime fields | Custom template |
| User-space dependencies, repository checkout, package cache, generated files, or per-project tool configuration on an existing base image | Builtin template plus rootfs snapshot and claim with `snapshot_id` |
| Data that must outlive sandbox identity, be mounted by multiple sandboxes, or be accessed through direct storage APIs | Sandbox Volume |

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. Snapshot and fork accept a running or paused source sandbox. Restore remains useful for rolling back an existing paused sandbox. See [Snapshot And Restore](/docs/sandbox/snapshot-restore) for the lifecycle requirements and API workflow, or read [Initialize Once, Claim Many](/blog/2026-06/initialize-once-fork-many-custom-ai-agent-sandboxes) 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.

<Endpoint method="POST">
/api/v1/templates
</Endpoint>

### Request Body

| Field | Type | Description |
|-------|------|-------------|
| `template_id` | string | Unique identifier for the template (e.g., `my-python-env`) |
| `spec` | object | Full template specification |

<Callout variant="info">
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.
</Callout>

**Example spec file (`template.yaml`):**

```yaml
spec:
    mainContainer:
        image: python:3.12-slim
        resources:
            cpu: "1"
            memory: 4Gi
            ephemeralStorage: 8Gi
    pool:
        minIdle: 2
        maxIdle: 10
```

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `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{
                CPU:              apispec.NewOptString("1"),
                Memory:           apispec.NewOptString("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)`
    },
    {
      label: "Python",
      language: "python",
      code: `from sandbox0.apispec.models.template_create_request import TemplateCreateRequest
from sandbox0.apispec.models.sandbox_template_spec import SandboxTemplateSpec

tpl = client.create_template(TemplateCreateRequest(
    template_id="my-python-env",
    spec=SandboxTemplateSpec.from_dict({
        "mainContainer": {
            "image": "python:3.12-slim",
            "resources": {"cpu": "1", "memory": "4Gi", "ephemeralStorage": "8Gi"},
        },
        "pool": {"minIdle": 2, "maxIdle": 10},
    }),
))
print(f"Template created: {tpl.template_id}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `var tpl = await client.templates.create({
    templateId: "my-python-env",
    spec: {
        mainContainer: {
            image: "python:3.12-slim",
            resources: { cpu: "1", memory: "4Gi", ephemeralStorage: "8Gi" },
        },
        pool: { minIdle: 2, maxIdle: 10 },
    },
});
console.log("Template created:", tpl.templateId);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `# Create from a YAML spec file
s0 template create --id my-python-env --spec-file template.yaml`
    }
  ]}
/>

---

## Get Template

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

<Endpoint method="GET">
/api/v1/templates/{'{id}'}
</Endpoint>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `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)`
    },
    {
      label: "Python",
      language: "python",
      code: `tpl = client.get_template("my-python-env")
print(f"Template: {tpl.template_id} (scope: {tpl.scope})")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `tpl = await client.templates.get("my-python-env");
console.log("Template:", tpl.templateId, "scope:", tpl.scope);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 template get my-python-env`
    }
  ]}
/>

---

## List Templates

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

<Endpoint method="GET">
/api/v1/templates
</Endpoint>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `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)
}`
    },
    {
      label: "Python",
      language: "python",
      code: `templates = client.list_templates()
for tpl in templates:
    print(f"- {tpl.template_id} ({tpl.scope})")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const templates = await client.templates.list();
for (const tpl of templates) {
    console.log(\`- \${tpl.templateId} (\${tpl.scope})\`);
}`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 template list`
    }
  ]}
/>

---

## Update Template

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

<Endpoint method="PUT">
/api/v1/templates/{'{id}'}
</Endpoint>

<Callout variant="warning">
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.
</Callout>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `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{
                CPU:              apispec.NewOptString("2"),
                Memory:           apispec.NewOptString("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)`
    },
    {
      label: "Python",
      language: "python",
      code: `from sandbox0.apispec.models.template_update_request import TemplateUpdateRequest
from sandbox0.apispec.models.sandbox_template_spec import SandboxTemplateSpec

tpl = client.update_template(
    "my-python-env",
    TemplateUpdateRequest(
        spec=SandboxTemplateSpec.from_dict({
            "mainContainer": {
                "image": "python:3.12-slim",
                "resources": {"cpu": "2", "memory": "8Gi", "ephemeralStorage": "8Gi"},
            },
            "pool": {"minIdle": 3, "maxIdle": 10},
        }),
    ),
)
print(f"Template updated: {tpl.template_id}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `tpl = await client.templates.update("my-python-env", {
    spec: {
        mainContainer: {
            image: "python:3.12-slim",
            resources: { cpu: "2", memory: "8Gi", ephemeralStorage: "8Gi" },
        },
        pool: { minIdle: 3, maxIdle: 10 },
    },
});
console.log("Template updated:", tpl.templateId);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `# Update from an updated spec file
s0 template update my-python-env --spec-file template.yaml`
    }
  ]}
/>

---

## Delete Template

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

<Endpoint method="DELETE">
/api/v1/templates/{'{id}'}
</Endpoint>

<Callout variant="warning">
Deleting a template does not terminate running Sandboxes created from it, but no new Sandboxes can be created from the deleted template.
</Callout>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `_, err = client.DeleteTemplate(ctx, "my-python-env")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Template deleted")`
    },
    {
      label: "Python",
      language: "python",
      code: `client.delete_template("my-python-env")
print("Template deleted")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `await client.templates.delete("my-python-env");
console.log("Template deleted");`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 template delete my-python-env`
    }
  ]}
/>

---

## Next Steps

<CardGroup>
  <Card title="Custom Images" href="/docs/sandbox/template/images" cta="Continue">
    Build and reference custom container images for sandbox templates.
  </Card>

  <Card title="Warm Pool" href="/docs/sandbox/template/pool" cta="Continue">
    Use warm pools to reduce startup latency for common templates.
  </Card>

  <Card title="Snapshot And Restore" href="/docs/sandbox/snapshot-restore" cta="Continue">
    Reuse initialized rootfs state without creating another template.
  </Card>

  <Card title="Initialize Once, Claim Many" href="/blog/2026-06/initialize-once-fork-many-custom-ai-agent-sandboxes" cta="Read">
    Customize a builtin template with rootfs snapshots and claim-time snapshot IDs.
  </Card>
</CardGroup>
