# 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, and declare warm processes that start as soon as the template pod enters the warm pool. Procd remains PID 1 and owns those processes. See [Warm Processes](/docs/template/warm-processes) for the dedicated guide.

---

## 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
    pool:
        minIdle: 2
        maxIdle: 10
```

**Example with a template-started warm process:**

```yaml
spec:
    mainContainer:
        image: ubuntu:24.04
        resources:
            cpu: "1"
            memory: 4Gi
    warmProcesses:
        - name: codex
          type: cmd
          alias: codex
          command: ["/bin/sh", "-lc", "/app/start-codex.sh"]
          cwd: /workspace
          envVars:
              MODE: warm
          probes:
              readiness:
                  process: {}
    pool:
        minIdle: 2
        maxIdle: 10
```

Warm processes start during template pod creation, before the pod is claimable from the warm pool. They are intended for long-running helpers such as agents, language servers, and daemon-style workers. SandboxProbe readiness keeps unhealthy helpers out of the warm pool, and SandboxProbe liveness hands restart back to Kubernetes by terminating procd on failure.

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `tpl, err := client.CreateTemplate(ctx, apispec.TemplateCreateRequest{
    TemplateID: "my-codex-env",
    Spec: apispec.SandboxTemplateSpec{
        MainContainer: apispec.NewOptContainerSpec(apispec.ContainerSpec{
            Image: "ubuntu:24.04",
            Resources: apispec.ResourceQuota{
                CPU:    apispec.NewOptString("1"),
                Memory: apispec.NewOptString("4Gi"),
            },
        }),
        WarmProcesses: []apispec.WarmProcessSpec{
            {
                Name:    apispec.NewOptString("codex"),
                Type:    apispec.WarmProcessSpecTypeCmd,
                Alias:   apispec.NewOptString("codex"),
                Command: []string{"/bin/sh", "-lc", "/app/start-codex.sh"},
                CWD:     apispec.NewOptString("/workspace"),
                EnvVars: map[string]string{"MODE": "warm"},
            },
        },
        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-codex-env",
    spec=SandboxTemplateSpec.from_dict({
        "mainContainer": {
            "image": "ubuntu:24.04",
            "resources": {"cpu": "1", "memory": "4Gi"},
        },
        "warmProcesses": [{
            "name": "codex",
            "type": "cmd",
            "alias": "codex",
            "command": ["/bin/sh", "-lc", "/app/start-codex.sh"],
            "cwd": "/workspace",
            "envVars": {"MODE": "warm"},
            "probes": {"readiness": {"process": {}}},
        }],
        "pool": {"minIdle": 2, "maxIdle": 10},
    }),
))
print(f"Template created: {tpl.template_id}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const tpl = await client.templates.create({
    templateId: "my-codex-env",
    spec: {
        mainContainer: {
            image: "ubuntu:24.04",
            resources: { cpu: "1", memory: "4Gi" },
        },
        warmProcesses: [{
            name: "codex",
            type: "cmd",
            alias: "codex",
            command: ["/bin/sh", "-lc", "/app/start-codex.sh"],
            cwd: "/workspace",
            envVars: { MODE: "warm" },
            probes: { readiness: { process: {} } },
        }],
        pool: { minIdle: 2, maxIdle: 10 },
    },
});
console.log("Template created:", tpl.templateId);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `cat >template.yaml <<'EOF'
spec:
    mainContainer:
        image: ubuntu:24.04
        resources:
            cpu: "1"
            memory: 4Gi
    warmProcesses:
        - name: codex
          type: cmd
          alias: codex
          command: ["/bin/sh", "-lc", "/app/start-codex.sh"]
          cwd: /workspace
          envVars:
              MODE: warm
          probes:
              readiness:
                  process: {}
    pool:
        minIdle: 2
        maxIdle: 10
EOF

s0 template create --id my-codex-env --spec-file template.yaml`
    }
  ]}
/>

<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"),
            },
        }),
        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"},
        },
        "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" },
        },
        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"),
            },
        }),
        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"},
            },
            "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" },
        },
        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/template/images" cta="Continue">
    Build and reference custom container images for sandbox templates.
  </Card>

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