# Templates

A template is the image a sandbox boots from. Impello ships a set of
defaults, and you pick one by name when you create a sandbox.

## Choose a template

Pass the name to `Sandbox.create`. With no name you get `base`.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('codex')
const result = await sandbox.commands.run('codex --version')

console.log(result.stdout)
await sandbox.kill()
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create("codex")
result = sandbox.commands.run("codex --version")

print(result.stdout)
sandbox.kill()
```

</Tab>
</CodeTabs>

Both SDKs read your key from `IMPELLO_API_KEY`; see
[API keys](/docs/api-keys). In TypeScript the name can also go in the options
object as `template`, and in Python as the `template` keyword argument.

## The default templates

Every default ends as the user `user` with the working directory
`/home/user`, so a command you run lands there. `codex`, `opencode`, `pi`,
`amp`, `droid`, `grok`, `openclaw` and `k3s` start from `base`. `claude`,
`code-interpreter`, `desktop` and `omarchy` are built from their own image, so
they do not carry `base`'s toolchain.

<div className="docs-table-wrap">
<table>
  <thead>
    <tr><th>Name</th><th>What it holds</th><th>vCPU</th><th>Memory</th></tr>
  </thead>
  <tbody>
    <tr><td>`base`</td><td>Debian 12 with Python 3.12, Node 22, git, GitHub CLI, jq, ripgrep, curl, unzip, yarn, pnpm and build tools. The default.</td><td>2</td><td>1 GB</td></tr>
    <tr><td>`claude`</td><td>Claude Code on Ubuntu 25.04, with Docker enabled at boot, Node 22, Python 3, pipx and uv. Binary at `~/.local/bin/claude`.</td><td>2</td><td>2 GB</td></tr>
    <tr><td>`codex`</td><td>`base` plus the Codex CLI, installed globally with npm.</td><td>2</td><td>2 GB</td></tr>
    <tr><td>`opencode`</td><td>`base` plus the OpenCode CLI, installed globally with npm.</td><td>2</td><td>2 GB</td></tr>
    <tr><td>`pi`</td><td>`base` plus the pi coding agent, installed globally with npm.</td><td>2</td><td>2 GB</td></tr>
    <tr><td>`amp`</td><td>`base` plus the Amp CLI at `~/.local/bin/amp`.</td><td>2</td><td>2 GB</td></tr>
    <tr><td>`droid`</td><td>`base` plus the Droid CLI at `~/.local/bin/droid`.</td><td>2</td><td>2 GB</td></tr>
    <tr><td>`grok`</td><td>`base` plus the Grok CLI at `~/.grok/bin/grok`.</td><td>2</td><td>2 GB</td></tr>
    <tr><td>`openclaw`</td><td>`base` plus the OpenClaw CLI at `~/.npm-global/bin/openclaw`.</td><td>2</td><td>4 GB</td></tr>
    <tr><td>`code-interpreter`</td><td>Python 3.13 with Jupyter kernels for Python, R, JavaScript, Bash and Java. Jupyter starts with the sandbox.</td><td>2</td><td>2 GB</td></tr>
    <tr><td>`k3s`</td><td>`base` plus single-node Kubernetes (k3s) and `kubectl`. The cluster starts at boot and `KUBECONFIG` is set.</td><td>4</td><td>8 GB</td></tr>
    <tr><td>`desktop`</td><td>Ubuntu 22.04 with XFCE, Firefox, Chrome, VS Code, LibreOffice, x11vnc and noVNC. The GUI stack is installed but no start command is declared, so a sandbox from this template boots to a shell only. There is no supported way to start the desktop yet; use `omarchy` for a browser-reachable desktop.</td><td>4</td><td>8 GB</td></tr>
    <tr><td>`omarchy`</td><td>Arch Linux with the Omarchy shell on a sway session, served over noVNC on port 6080.</td><td>4</td><td>8 GB</td></tr>
  </tbody>
</table>
</div>

The CLIs installed with npm (`codex`, `opencode`, `pi`) are on the `PATH`. The
vendor installers put their binary under the home directory at the path shown,
and the template appends that directory to `~/.bashrc`. `commands.run` runs
your command with `bash -l -c`. A login shell reads `~/.profile`, not the end
of `~/.bashrc`, so that appended line does not apply — call the binary by its
full path.

## Aliases

Two templates answer to a second name. `claude` is also `claude-code`, and
`code-interpreter` is also `code-interpreter-v1`. Either name creates the same
sandbox.

## Run an agent CLI

The `base`-derived agent templates (`codex`, `opencode`, `pi`, `amp`, `droid`,
`grok`, `openclaw`) are `base` plus one CLI; `claude` is its own Ubuntu image
with Docker, uv and pipx. In every one, nothing is signed in — pass the
vendor's key with `envs` when you create the sandbox. Those variables are set
for every command you run, and `envs` on a single command overrides them.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const key = process.env.ANTHROPIC_API_KEY
if (!key) throw new Error('Set ANTHROPIC_API_KEY')

const sandbox = await Sandbox.create('claude', {
  envs: { ANTHROPIC_API_KEY: key },
  timeoutMs: 600_000,
})

const result = await sandbox.commands.run(
  '~/.local/bin/claude -p "List the files in this directory"',
  { timeoutMs: 300_000 }
)

console.log(result.stdout)
await sandbox.kill()
```

</Tab>
<Tab label="Python">

```python
import os
from impello import Sandbox

sandbox = Sandbox.create(
    "claude",
    envs={"ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"]},
    timeout=600,
)

result = sandbox.commands.run(
    '~/.local/bin/claude -p "List the files in this directory"',
    timeout=300,
)

print(result.stdout)
sandbox.kill()
```

</Tab>
</CodeTabs>

The pattern is the same for all of them: create by name, pass the vendor's key
in `envs`, run the binary. `-p` is Claude Code's print mode, which runs one
prompt and writes the answer to stdout; each vendor's CLI has its own flags, so
check the vendor's documentation. A command gives up after 60 seconds unless
you raise `timeoutMs` (`timeout`, in seconds, in Python); see
[Run commands](/docs/sandbox/commands).

## Reach a service in a template

Two templates declare a start command and wait for it before the sandbox is
ready: `omarchy` (noVNC on 6080) and `code-interpreter` (an HTTP server on
49999). `k3s` starts its cluster from a systemd unit instead, shortly after
boot. `getHost(port)` returns the host for a port; you supply the scheme and
the path.

The omarchy desktop is served at the path `/vnc.html` on port 6080.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('omarchy', {
  timeoutMs: 1_800_000,
})

console.log(`https://${sandbox.getHost(6080)}/vnc.html`)
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create(
    "omarchy",
    timeout=1800,
)

print(f"https://{sandbox.get_host(6080)}/vnc.html")
```

</Tab>
</CodeTabs>

<Callout kind="warning">
`network.allowPublicTraffic` defaults to `true`, so that URL is reachable by
anyone who has it — including the desktop it opens. Create the sandbox with
`{ network: { allowPublicTraffic: false } }` in TypeScript, or
`network={"allow_public_traffic": False}` in Python, to close the port to
unauthenticated callers; a caller then has to present the sandbox's traffic
access token. See
[Network and public URLs](/docs/sandbox/network#restrict-inbound-traffic).
</Callout>

`code-interpreter` starts Jupyter with the sandbox and an HTTP server on port
49999 whose health check is `/health`. Neither SDK ships a client for that
server, so run code in this template with `commands.run`, for example
`python3 -c "print(1 + 1)"`.

## Sizes

Each template declares the vCPU count and memory it is built with, listed in
the table above. Your plan sets the largest sandbox you can run: Micro's
ceiling is 2 vCPU and 4 GB per sandbox, and Base and Scale are 4 vCPU and 8 GB.
`k3s`, `desktop` and `omarchy` are built at 4 vCPU and 8 GB, so they need Base
or Scale; the other ten fit inside Micro. See
[Limits and pricing](/docs/limits-and-pricing).

## Updates

The defaults are rebuilt every week. The agent CLIs are installed unpinned, so
each rebuild picks up the vendor's current release and the version inside a
fresh sandbox moves with it. To freeze a version, build your own template from
a default with `fromTemplate('claude')` and create sandboxes from that name
instead.

## Check that a name exists

`Template.exists` returns `true` when a name you own resolves. It lives in the
`@impello/sdk/template` subpath and is TypeScript only; the Python SDK has no
template module.

```ts
import { Template } from '@impello/sdk/template'

const exists = await Template.exists('my-app')

console.log(exists)
```

## Build your own

When no default fits, start from one of them or from any Docker image and add
what you need. The builder is TypeScript only, but the template it produces
can be used from either SDK by name.

Next: [Build a custom template](/docs/templates/build).
