Template gallery#

Official templates give your agent a working environment in one call: the runtime, libraries and tools are already installed, and the sandbox starts from a RAM snapshot of the booted system (target p95 < 500 ms), not from a cold boot.

sandbox = client.sandboxes.create(template="python")
print(sandbox.exec(command="python -c 'import pandas; print(pandas.__version__)'").stdout)

Every official template is built from a declarative recipe with pinned versions and digests, ships with an SBOM, stays within a size budget, and passes an in-sandbox smoke test (taskcloud-smoke) before release.

Private beta. The gallery is rolling out region by region. GET /templates?owner=builtin lists the templates available to your account; base and desktop are available everywhere.

Template What's inside Default size Ports Good for
base Ubuntu 24.04, systemd, Docker, Python 3.12, git, build-essential default – anything; the default
python Python 3.12 (default venv) and 3.13 via uv; NumPy, pandas, Polars, SciPy, scikit-learn, DuckDB, PyArrow, Matplotlib default – data analysis, code interpreters, ML without GPU
node Node.js 22 LTS, npm, pnpm, Bun, TypeScript default – JS/TS agents, web app builders
browser headless Chromium, Playwright for Python and Node, Noto/CJK/emoji fonts, Xvfb default – browser automation, scraping, computer use without a desktop
coding-agent git, gh, ripgrep, fd, build-essential, Docker in the VM, Node 22, Python 3.12; Codex CLI, Gemini CLI, OpenCode, Aider default – coding agents, CI-like tasks, repo work
jupyter JupyterLab (already running), Python 3.12 data stack default 8888 notebooks, data exploration
desktop base + XFCE, KasmVNC, Chromium, Firefox, xdotool default – computer use with a visible desktop, see Desktop and computer use

All templates run as user user (uid 1000) with passwordless sudo, have SSH, and keep the full disk persistent across pause and resume. Templates are not tied to a size: pass size to override the default (desktop needs default or large).

Create a sandbox from a template#

Use the template name, or its ID (tpl_python). The same call works for every template.

TypeScript#

import { Client } from "@taskcloud/sdk";

const client = new Client();
const sandbox = await client.sandboxes.create({ template: "python" });
const result = await sandbox.exec({ command: "taskcloud-smoke" });
console.log(result.exit_code, result.stdout); // 0 ... TASKCLOUD_SMOKE_OK python

Python#

from taskcloud import Client

client = Client()
sandbox = client.sandboxes.create(template="python")
result = sandbox.exec(command="taskcloud-smoke")
print(result.exit_code, result.stdout)  # 0 ... TASKCLOUD_SMOKE_OK python

CLI#

id=$(taskcloud create --template python --json | jq -r .id)
taskcloud exec "$id" -- taskcloud-smoke
taskcloud template ls          # the gallery and your own templates

curl#

curl -sS -X POST https://api.task.cloud/v1/sandboxes \
  -H "Authorization: Bearer $TASKCLOUD_API_KEY" -H "Content-Type: application/json" \
  -d '{"template":"python","name":"analysis-1"}'

curl -sS "https://api.task.cloud/v1/templates?owner=builtin" -H "Authorization: Bearer $TASKCLOUD_API_KEY"

MCP#

The create_sandbox tool of the MCP server describes the official templates, so an agent can pick one by itself. Tool call:

{ "name": "create_sandbox", "arguments": { "template": "browser" } }

Templates#

base#

Ubuntu 24.04 with systemd, Docker, Python 3.12 (system python3), git and build-essential. This is what you get without a template. Built with debootstrap rather than from an OCI image.

const sandbox = await client.sandboxes.create({ template: "base" });
await sandbox.exec({ command: "docker run --rm alpine echo hello" });

python#

The default virtualenv /opt/taskcloud/venv (Python 3.12.16) is on PATH and owned by user, so pip install and uv pip install work without sudo. Python 3.13 is installed for uv: uv run --python 3.13 .... Matplotlib uses the Agg backend (no display).

sandbox = client.sandboxes.create(template="python")
sandbox.files.write("/home/user/sales.csv", "region,amount\neu,10\nus,32\n")
r = sandbox.exec(command="python -c \"import pandas as pd; print(pd.read_csv('sales.csv').amount.sum())\"")
print(r.stdout)  # 42

node#

Node.js 22 LTS in /opt/taskcloud/node (owned by user: npm install -g works without sudo), pnpm, Bun and TypeScript.

const sandbox = await client.sandboxes.create({ template: "node" });
await sandbox.exec({ command: "pnpm create vite@latest app -- --template react-ts && cd app && pnpm install" });
await sandbox.exec({ command: "cd app && pnpm dev --host 0.0.0.0 --port 5173", background: true });
const port = await sandbox.ports.expose({ port: 5173, visibility: "public" });
console.log(port.url);

browser#

Headless Chromium from Playwright, shared by the Python and Node bindings (same Playwright version, one browser build in /ms-playwright). Fonts cover Latin, CJK and emoji, so screenshots of real pages render correctly. For headed runs use xvfb-run; for a visible desktop use desktop.

sandbox = client.sandboxes.create(template="browser")
script = """
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
    page = p.chromium.launch().new_page()
    page.goto("https://example.com")
    page.screenshot(path="/home/user/shot.png")
    print(page.title())
"""
sandbox.files.write("/home/user/shot.py", script)
print(sandbox.exec(command="python shot.py").stdout)
png = sandbox.files.read("/home/user/shot.png")  # bytes

From Node, require("playwright") works out of the box (NODE_PATH points at the global modules). In your own project, install the same version (npm i playwright@1.64.1) and it reuses the installed browser.

coding-agent#

Everything a coding agent needs in a repository: git, the GitHub CLI, ripgrep, fd, build-essential, Docker (the daemon runs inside the VM; user is in the docker group), Node 22 and Python 3.12.

Agent CLIs: open-source ones are preinstalled (Codex CLI, Gemini CLI, OpenCode, Aider). Proprietary CLIs are not redistributed in the image; install them inside the sandbox from the vendor, under the vendor's terms, with taskcloud-agents install:

taskcloud-agents list                    # what is installed, licenses, which env var each CLI reads
taskcloud-agents install claude-code     # npm install -g @anthropic-ai/claude-code (vendor's official method)

No credentials are in the image. Pass API keys as sandbox env when you create the sandbox:

const sandbox = await client.sandboxes.create({
  template: "coding-agent",
  env: { OPENAI_API_KEY: process.env.OPENAI_API_KEY!, GH_TOKEN: process.env.GH_TOKEN! },
});
await sandbox.exec({ command: "gh repo clone acme/app && cd app && codex exec 'fix the failing test'", timeout_seconds: 1800 });

jupyter#

JupyterLab is already running on port 8888 when the sandbox starts. Every sandbox has its own token: pass JUPYTER_TOKEN in env at create, or read the generated one with taskcloud-jupyter-token. A token or login cookie from another sandbox (including a fork) does not work.

import secrets

token = secrets.token_urlsafe(32)
sandbox = client.sandboxes.create(
    template="jupyter",
    env={"JUPYTER_TOKEN": token},
    ports=[{"port": 8888, "visibility": "public"}],
)
print(f"{sandbox.ports.url(8888)}/lab?token={token}")
const sandbox = await client.sandboxes.create({ template: "jupyter", ports: [{ port: 8888, visibility: "public" }] });
const token = (await sandbox.exec({ command: "taskcloud-jupyter-token" })).stdout.trim();
console.log(`${sandbox.ports.url(8888)}/lab?token=${token}`);

With visibility: "private" (the default) the edge also requires a sandbox access token; see Networking. Health check without a token: curl -fsS http://127.0.0.1:8888/api.

desktop#

base plus an XFCE desktop streamed with KasmVNC, Chromium and Firefox. The desktop is already running. See Desktop and computer use for screenshots, input and the stream URL.

Smoke tests#

Every official template contains taskcloud-smoke, the same test we run before each release. It needs no network and no credentials, prints one line per check and ends with TASKCLOUD_SMOKE_OK <template> (exit code 0) or TASKCLOUD_SMOKE_FAIL <template> (exit code 1). Run it to check a sandbox after you changed it:

taskcloud exec sbx_2fqxnt4bkq7m -- taskcloud-smoke
Template What taskcloud-smoke checks
base user, sudo, Python, compiler, Docker
python Python 3.12 and 3.13, NumPy/pandas/Polars/scikit-learn/DuckDB, a Matplotlib PNG, a new venv with uv
node Node 22, npm, pnpm, Bun running TypeScript, tsc
browser a Chromium screenshot from Python and from Node, fonts, a headed run under Xvfb
coding-agent git + ripgrep in a repo, gh, a C build, Docker, each agent CLI, no credentials in the image
jupyter /api health, token required (403 without), a kernel start and stop
desktop KasmVNC, X display, a desktop screenshot, Chromium and Firefox

How official templates are built#

  • Recipes: a Dockerfile per template on Ubuntu 24.04 pinned by digest (base and desktop: debootstrap), converted to a VM root filesystem by our template builder, booted, and snapshotted with RAM.
  • Pinned versions: language runtimes and tools by exact version, downloads checked against SHA-256, Python packages from hash-locked requirement files, Ubuntu packages from a dated archive snapshot.
  • SBOM: an SPDX and a CycloneDX SBOM per release, plus a package list at /usr/share/doc/taskcloud-template/packages.txt inside the sandbox.
  • Size budget: the release fails if an image grows past its budget, so start times and storage stay predictable.
  • Metadata: /etc/taskcloud/template.json in the sandbox names the template and version.

Current versions: Python 3.12.16 / 3.13.18, uv 0.13.7, Node.js 22.23.4, pnpm 12.18.0, Bun 1.4.4, TypeScript 7.0.2, Playwright 1.64.1, JupyterLab 4.6.6, gh 2.103.3, Codex CLI 0.167.0, Gemini CLI 0.67.0, OpenCode 1.19.5, Aider 0.86.2.

Listing templates#

GET /templates returns official templates with official: true, tags, ports, docs_url and example_command; your own templates have official: false and empty tags and ports.

{
  "id": "tpl_jupyter",
  "name": "jupyter",
  "official": true,
  "builtin": true,
  "state": "ready",
  "size": "default",
  "tags": ["jupyter", "notebooks", "python", "data"],
  "ports": [{ "port": 8888, "name": "jupyterlab", "protocol": "http", "description": "JupyterLab; ..." }],
  "docs_url": "https://task.cloud/docs/templates.html#jupyter"
}

If your account already has a template with the same name as an official one (for example your own python), the name keeps resolving to yours; use the ID (tpl_python) for the official one.

Customize#

Start from an official template, install what you need, then save it as your own template so every new sandbox starts with it:

const sandbox = await client.sandboxes.create({ template: "python" });
await sandbox.exec({ command: "pip install transformers==4.57.0 && python -c 'import transformers'" });
const snap = await sandbox.snapshots.create({ name: "py-transformers" });
await client.templates.create({ name: "py-transformers", snapshot_id: snap.id });

Or build a template from your own OCI image; see Templates.