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=builtinlists the templates available to your account;baseanddesktopare available everywhere.
Gallery#
| 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 (
baseanddesktop: 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.txtinside 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.jsonin 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.
Related#
- Templates: versions, building your own from an image or a snapshot
- Exec and files
- Networking: ports and access tokens
- API reference