Templates capture setup once so every machine can start from a ready environment.
from nullspace import Machine, Template, default_build_logger

builder = (
    Template()
    .from_python_image("3.12")
    .apt_install(["git", "curl"])
    .pip_install(["fastapi", "uvicorn"])
    .copy("./app.py", "/workspace/app.py")
    .set_workdir("/workspace")
)

build = Template.build(
    builder,
    name="fastapi-agent",
    tags=["stable"],
    on_log_entry=default_build_logger(),
)

with Machine.create(template=build.canonical_ref) as machine:
    result = machine.commands.run("python3 --version", shell=True)
    print(result.stdout)
import { NullspaceClient } from "@nullspace/sdk";

const client = new NullspaceClient();

const template = await client.templates
  .builder()
  .fromPythonImage("3.12")
  .aptInstall(["git", "curl"])
  .pipInstall(["fastapi", "uvicorn"])
  .copy("./app.py", "/workspace/app.py")
  .setWorkdir("/workspace")
  .build({
    name: "fastapi-agent",
    tags: ["stable"],
    onLogEntry: (entry) => console.log(entry.message),
  });

await using machine = await client.machines.create({
  template: `${template.name}:stable`,
});
const result = await machine.commands.run("python3 --version", { shell: true });
console.log(result.stdout);
nullspace template build \
  --from-python-image 3.12 \
  --apt-install git,curl \
  --pip-install fastapi,uvicorn \
  --copy-src ./app.py --copy-dst /workspace/app.py \
  --set-workdir /workspace \
  --name fastapi-agent \
  --tag stable

nullspace machine create --template fastapi-agent:stable
# Template builds stream structured logs over SSE; the request body is large.
# See the API reference for the full CreateTemplateRequest schema.
curl -N -X POST "${NULLSPACE_API_URL}/v1/templates/build" \
  -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "fastapi-agent",
    "tags": ["stable"],
    "base_image": "python:3.12",
    "steps": [
      {"action": "apt_install", "packages": ["git", "curl"]},
      {"action": "pip_install", "packages": ["fastapi", "uvicorn"]},
      {"action": "workdir", "path": "/workspace"}
    ]
  }'
Use Template.build_in_background() when a build should continue while your client disconnects or polls progress later.
build = Template.build_in_background(builder, name="fastapi-agent", tags=["next"])
finished = build.wait_until_terminal()
print(finished.build.status)
nullspace template build \
  --from-python-image 3.12 \
  --name fastapi-agent \
  --tag next \
  --background

nullspace template wait tb_... --poll-interval-ms 500
# POST /v1/templates/builds accepts the same CreateTemplateRequest body and
# returns a build handle to poll with GET /v1/templates/builds/{build_id}.
curl -X POST "${NULLSPACE_API_URL}/v1/templates/builds" \
  -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "fastapi-agent",
    "tags": ["next"],
    "base_image": "python:3.12"
  }'
The TypeScript SDK builds synchronously over the SSE stream; for background builds and polling, use the Python SDK, CLI, or HTTP API.
See the API reference for the full template build request schema and background-build polling.