PLATINUM DOCS

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.json
curl -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_imageany Docker image ref
fromPythonImage(v) / from_python_imagepython:<v>
fromNodeImage(v) / from_node_imagenode:<v>
fromBunImage(v) / from_bun_imageoven/bun:<v>
fromUbuntuImage(v) / from_ubuntu_imageubuntu:<v>
fromDebianImage(v) / from_debian_imagedebian:<v>
fromAlpineImage(v) / from_alpine_imagealpine:<v>

Add steps

Every step returns the builder, so calls chain. TS is camelCase, Python is snake_case.

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, ...).

OptionDefaultNotes
namerequired^[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_cpu11–16 — sandbox default when create does not override
default_ram_mb512128–32768
default_disk_gb21–100
size_mb1024initial rootfs size in MB; per-org cap 20,480
wait_ms300000how 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.json
curl -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
    }
  }'
RuleBehavior
image + templateMutually exclusive — sending both is a 400. Omit both to use the default pt-base template.
CachingAn identical spec reuses the cached build; a new spec builds; a previously failed one rebuilds.
Hash coverageOnly base_image, steps, entrypoint, and ready_cmd count. A change to size_mb or a default_* still hits the old cached build.
Cache scopePer-org — two orgs with identical specs each pay their own first build.
size_mbCaps 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-spec request/response schemas