Skip to main content
Templates let you prepare dependencies once and launch sandboxes from the resulting environment.

Build from Python

from nullspace import Template, wait_for_port

builder = (
    Template.from_python_image("3.12")
    .apt_install(["git", "curl"])
    .pip_install(["fastapi", "uvicorn"])
    .copy("./app.py", "/workspace/app.py")
    .set_workdir("/workspace")
    .set_start_cmd("uvicorn app:app --host 0.0.0.0 --port 8080", readiness=wait_for_port(8080))
)

build = Template.build(builder, name="fastapi-app")
print(build.status)
The start command runs during template build before snapshotting. Sandboxes created from the template restore that ready running state instead of rerunning the command at create time. Warm pools use that same ready snapshot as their boot contract. Keep services poolable by putting required startup envs in template runtime defaults and by using readiness probes that succeed before snapshotting. Create-time envs, volumes, cwd overrides, desktop settings, and per-create networking options are not available to the prewarmed service boot path. Create and wait for a template warm pool through the SDK:
from nullspace import TemplateWarmPool

pool = TemplateWarmPool.create(
    "team/fastapi-app:stable",
    name="fastapi-small",
    min_ready=2,
    max_ready=4,
    vcpus=2,
    memory_mb=1024,
)
pool.wait_until_ready(min_ready=2)
Template.create_warm_pool(...) is available when you already have a template object. See Template Warm Pools for checkout modes, status fields, and drain behavior.

Build-time and runtime envs

Nullspace deliberately separates env lifetime:
Use caseNullspace methodVisibility
Build-only valuesset_build_envs({...})Build steps only
Runtime defaultsset_runtime_envs({...})Persisted into sandboxes
Create-time overridesSandbox.create(envs={...})One sandbox
set_runtime_envs() values are also visible during later child template builds as inherited defaults. set_build_envs() does not persist into launched sandboxes.

Sources

Template.from_ubuntu_image("22.04")
Template.from_debian_image()
Template.from_node_image("20")
Template.from_bun_image("1.2")
Template.from_image("registry.example.com/team/app:1.0")
Template.from_dockerfile("./Dockerfile")
Template.from_dockerfile_content("FROM ubuntu:22.04\nRUN apt-get update")
Template.from_template("base")
For private registries, configure registry auth before build:
builder = Template.from_aws_registry(
    "123456789012.dkr.ecr.us-east-1.amazonaws.com/app:latest",
    access_key_id="...",
    secret_access_key="...",
    region="us-east-1",
)
builder = Template.from_gcp_registry(
    "gcr.io/project/app:latest",
    service_account_json="service-account.json",
)
Use set_file_context() for local Dockerfile-style copy contexts, and use to_json() or to_dockerfile() to inspect the generated template before building.

Build options

build = Template.build(
    builder,
    name="fastapi-app",
    tags=["stable"],
    cpu_count=4,
    memory_mb=2048,
    skip_cache=True,
)
The same resource and cache options are available on Template.build_in_background() and builder.to_json(...).

Build logs

Fetch build logs after starting a template build:
nullspace template logs tb_123
nullspace template logs tb_123 --json
nullspace template logs tb_123 --since 2026-04-30T00:00:00Z
Native-backend Dockerfile instructions ignored with warning log entries: EXPOSE, VOLUME, ARG, LABEL. BuildKit-backed Dockerfiles use BuildKit failure and progress output instead. Ignored-instruction warnings include the Dockerfile line number and appear in the same structured build log stream used by TemplateBuild.get_status() and nullspace template logs.

Background builds

build = Template.build_in_background(builder, name="fastapi-app", tags=["next"])
connected = TemplateBuild.connect(build.build_id)
snapshot = connected.get_status(offset=0)
for entry in snapshot.entries:
    print(entry.kind, entry.message)

final = connected.wait_until_terminal(
    offset=snapshot.next_offset,
    on_log_entry=lambda entry: print(entry.message),
)
print(final.build.status)
Namespaces, reserved names, tags, aliases, and visibility are documented in Tags, Names, And Visibility. Use Template.list(fields=["id", "canonical_ref"]) and Template.get("team/template:tag", fields=["id", "canonical_ref"]) for compact raw dictionaries when a control plane page only needs selected fields. Concept: Create. API reference: buildTemplate, startTemplateBuild, getTemplateBuildStatus, and listTemplates.