Declarative builder
Define a template image as a chain of steps in code. Platinum caches builds by content.
Template defines an image as a chain of steps in code. Build the chain as a named template, or pass it inline when you create a sandbox. Platinum content-hashes each spec, so a repeat spec is a cache hit, not a rebuild.
Build a template
The step chain is SDK-only. The CLI and REST accept the compiled spec, not the chain.
A failed build does not throw. It returns state: "failed" with the tail of build_logs. Always check state.
Chain steps from a base image, then call build().
import { Platinum, Template } from "@platinum-dev/sdk";
const client = new Platinum({ url: process.env.PT_API_URL!, token: process.env.PT_TOKEN! });
const tpl = await Template
.fromPythonImage("3.13-slim")
.pipInstall(["pandas", "fastapi", "uvicorn"])
.aptInstall(["curl", "ripgrep"])
.env("LOG_LEVEL", "info")
.workdir("/app")
.copy("app.py", "/app/app.py")
.entrypoint("uvicorn app:app --host 0.0.0.0 --port 8000")
.build(client, { name: "my-agent", default_ram_mb: 1024 });
if (tpl.state === "failed") throw new Error(tpl.build_logs);import os
from platinum import Platinum, Template
client = Platinum(token=os.environ["PT_TOKEN"], api_url=os.environ["PT_API_URL"])
tpl = (Template.from_python_image("3.13-slim")
.pip_install(["pandas", "fastapi", "uvicorn"])
.apt_install(["curl", "ripgrep"])
.env("LOG_LEVEL", "info")
.workdir("/app")
.copy("app.py", "/app/app.py")
.entrypoint("uvicorn app:app --host 0.0.0.0 --port 8000")
.build(client, "my-agent", default_ram_mb=1024))
if tpl["state"] == "failed":
raise RuntimeError(tpl["build_logs"])# No dedicated CLI command for from-spec builds. Use the API passthrough.
pt api POST /v1/templates/from-spec -d @spec.jsoncurl -X POST $PT_API_URL/v1/templates/from-spec \
-H "Authorization: Bearer $PT_TOKEN" \
-d '{
"name": "my-agent",
"base_image": "python:3.13-slim",
"steps": [
{ "op": "pip", "packages": ["pandas", "fastapi", "uvicorn"] },
{ "op": "apt", "packages": ["curl", "ripgrep"] },
{ "op": "env", "key": "LOG_LEVEL", "value": "info" },
{ "op": "workdir", "path": "/app" }
],
"entrypoint": "uvicorn app:app --host 0.0.0.0 --port 8000",
"default_ram_mb": 1024
}'When the template is ready, create sandboxes with template: "my-agent". See Sandboxes.
Choose a base image
The build rejects floating tags: latest, lts, stable, main, master, edge, rolling. Pin a version (python:3.13-slim) or a @sha256: digest.
| Factory (TS / Python) | Base image |
|---|---|
Template.fromImage(ref) / from_image | any Docker image ref |
fromPythonImage(v) / from_python_image | python:<v> |
fromNodeImage(v) / from_node_image | node:<v> |
fromBunImage(v) / from_bun_image | oven/bun:<v> |
fromUbuntuImage(v) / from_ubuntu_image | ubuntu:<v> |
fromDebianImage(v) / from_debian_image | debian:<v> |
fromAlpineImage(v) / from_alpine_image | alpine:<v> |
Add steps
Every step returns the builder, so calls chain. TS is camelCase, Python is snake_case.
| Step (TS / Python) | Compiles to | Example |
|---|---|---|
.runCmd(cmd) / .run_cmd | RUN <cmd> | .runCmd("make install") |
.pipInstall(pkgs) / .pip_install | RUN pip install … | .pipInstall(["pandas"]) |
.npmInstall(pkgs) / .npm_install | RUN npm install -g … | .npmInstall(["typescript"]) |
.aptInstall(pkgs) / .apt_install | RUN apt-get install -y … | .aptInstall(["curl"]) |
.env(key, value) | ENV | .env("LOG_LEVEL", "info") |
.workdir(path) | WORKDIR | .workdir("/app") |
.user(user) | USER | .user("app") |
.copy(src, dst, mode?) | COPY — inlined, up to 256 KiB per file | .copy("app.py", "/app/app.py") |
.entrypoint(cmd) | template entrypoint, launched at boot | .entrypoint("node server.js") |
.readyCmd(cmd) / .ready_cmd | readiness check — see note | .readyCmd("curl -fsS localhost:8000/health") |
copy inlines a local file into the spec, up to 256 KiB. For larger files, write them after create with the filesystem API, or use a build context (see Templates).
readyCmd does not gate boot. A sandbox turns running when it boots, not when your app is ready. To wait for your app, poll its health endpoint via exec.
Configure build()
TS takes an options object: build(client, { name, ... }). Python takes name/version as arguments and the rest as keywords: build(client, "name", default_cpu=2, ...).
| Option | Default | Notes |
|---|---|---|
name | required | ^[a-z0-9][a-z0-9._-]*$, max 64 chars; unique per org. A rebuild of an existing name reuses its row. |
version | "1.0.0" | floating values rejected |
default_cpu | 1 | 1–16 — sandbox default when create does not override |
default_ram_mb | 512 | 128–32768 |
default_disk_gb | 2 | 1–100 |
size_mb | 1024 | initial rootfs size in MB; per-org cap 20,480 |
wait_ms | 300000 | how long to poll for ready/failed. 0 returns at once as building. A non-zero wait that expires throws PlatinumTimeoutError. The build keeps running — poll templates.get(id) to pick it up. |
The SDK polls every 3 s until the template leaves building.
Build inline at sandbox create
Inline builds must finish within 300 s. A slower build fails the create. For large images, build() a named template first, then create from it by name.
Pass the builder as image. Platinum builds and boots in one call.
const image = Template.fromImage("alpine:3.20").runCmd("apk add --no-cache curl jq");
const sbx = await client.sandboxes.create(
{ image, env: { GREETING: "hi" } },
{ waitForRunning: true, waitTimeoutMs: 600_000 },
);image = Template.from_image("alpine:3.20").run_cmd("apk add --no-cache curl jq")
sbx = client.sandboxes.create(image=image, env={"GREETING": "hi"},
wait_for_running=True, wait_timeout_ms=600_000)# No CLI flag for an inline image spec. Use the API passthrough.
pt api POST /v1/sandboxes --query "wait_for_state=running" -d @body.jsoncurl -X POST "$PT_API_URL/v1/sandboxes?wait_for_state=running&wait_timeout_ms=60000" \
-H "Authorization: Bearer $PT_TOKEN" \
-d '{
"image": {
"base_image": "alpine:3.20",
"steps": [{ "op": "run", "cmd": "apk add --no-cache curl jq" }],
"size_mb": 256
}
}'| Rule | Behavior |
|---|---|
image + template | Mutually exclusive — sending both is a 400. Omit both to use the default pt-base template. |
| Caching | An identical spec reuses the cached build; a new spec builds; a previously failed one rebuilds. |
| Hash coverage | Only base_image, steps, entrypoint, and ready_cmd count. A change to size_mb or a default_* still hits the old cached build. |
| Cache scope | Per-org — two orgs with identical specs each pay their own first build. |
size_mb | Caps at 20,480. |
See also
- Templates — other build routes, listing, updates, deletion, quotas
- Sandboxes — create options, wait semantics, lifecycle
- API reference — exact
/v1/templates/from-specrequest/response schemas