Install packages

from nullspace import Template

builder = (
    Template()
    .from_ubuntu_image("22.04")
    .apt_install(["git", "curl", "ripgrep"])
    .pip_install(["requests", "pytest"])
    .npm_install(["typescript"], g=True)
    .bun_install(["vite"], g=True)
)
const builder = client.templates
  .builder()
  .fromUbuntuImage("22.04")
  .aptInstall(["git", "curl", "ripgrep"])
  .pipInstall(["requests", "pytest"])
  .npmInstall(["typescript"], { global: true })
  .bunInstall(["vite"], { global: true });
nullspace template build \
  --from-ubuntu-image 22.04 \
  --apt-install git,curl,ripgrep \
  --pip-install requests,pytest \
  --npm-install typescript --npm-global \
  --bun-install vite --bun-global \
  --name agent-template
# Each builder step maps to an entry in the request "steps" array. Builds are
# large/SSE — 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": "agent-template",
    "base_image": "ubuntu:22.04",
    "steps": [
      {"action": "apt_install", "packages": ["git", "curl", "ripgrep"]},
      {"action": "pip_install", "packages": ["requests", "pytest"]},
      {"action": "npm_install", "packages": ["typescript"], "g": true},
      {"action": "bun_install", "packages": ["vite"], "g": true}
    ]
  }'
Common base-image helpers in both SDKs include debian_slim() / debianSlim(), from_registry() / fromRegistry(), from_name() / fromName(), from_ubuntu_image() / fromUbuntuImage(), from_python_image() / fromPythonImage(), and from_dockerfile_content() / fromDockerfileContent(). The Python SDK also exposes from_bun_image() and from_dockerfile(path). Package installers accept lists or varargs. For npm_install() and bun_install(), g=True performs a global install; TypeScript uses { global: true }.

Files, repos, and commands

builder = (
    Template()
    .from_python_image("3.12")
    .copy("./app.py", "/workspace/app.py")
    .copy_items([("./pyproject.toml", "/workspace/pyproject.toml")])
    .git_clone("https://github.com/pypa/sampleproject.git", "/workspace/sampleproject")
    .run_cmd("python3 -m compileall /workspace")
)
// Note: the TypeScript builder does not expose copyItems yet; use copy().
const builder = client.templates
  .builder()
  .fromPythonImage("3.12")
  .copy("./app.py", "/workspace/app.py")
  .copy("./pyproject.toml", "/workspace/pyproject.toml")
  .gitClone(
    "https://github.com/pypa/sampleproject.git",
    "/workspace/sampleproject",
  )
  .runCmd("python3 -m compileall /workspace");
nullspace template build \
  --from-python-image 3.12 \
  --copy-src ./app.py --copy-dst /workspace/app.py \
  --copy-src ./pyproject.toml --copy-dst /workspace/pyproject.toml \
  --git-clone https://github.com/pypa/sampleproject.git --git-path /workspace/sampleproject \
  --run-cmd "python3 -m compileall /workspace" \
  --name agent-template
# COPY steps need an uploaded build_context, so the raw build body is large.
# See the API reference for the full CreateTemplateRequest + build_context schema.
See the API reference for the full template build request and build_context upload schema. Use set_file_context() when a build needs a local context directory for copy steps. Use registry-auth helpers before pulling private images.

Environment, user, and workdir

builder = (
    builder
    .set_build_envs({"PIP_INDEX_URL": "https://example.invalid/simple"})
    .set_runtime_envs({"APP_ENV": "production"})
    .set_workdir("/workspace")
    .set_user("user")
)
builder
  .setBuildEnvs({ PIP_INDEX_URL: "https://example.invalid/simple" })
  .setRuntimeEnvs({ APP_ENV: "production" })
  .setWorkdir("/workspace")
  .setUser("user");
nullspace template build \
  --from-python-image 3.12 \
  --set-build-env PIP_INDEX_URL=https://example.invalid/simple \
  --set-runtime-env APP_ENV=production \
  --set-workdir /workspace \
  --set-user user \
  --name agent-template
# build_envs is a top-level object; runtime defaults (envs, user, workdir) live
# under runtime_config. Builds stream over SSE — see the API reference.
curl -N -X POST "${NULLSPACE_API_URL}/v1/templates/build" \
  -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "agent-template",
    "base_image": "python:3.12",
    "build_envs": {"PIP_INDEX_URL": "https://example.invalid/simple"},
    "runtime_config": {
      "default_envs": {"APP_ENV": "production"},
      "default_workdir": "/workspace",
      "default_user": "user"
    }
  }'
Build envs are visible only during build steps. Runtime envs are persisted into machines launched from the template.

File operations

builder = (
    builder
    .make_dir("/workspace/logs")
    .make_symlink("/workspace/app.py", "/usr/local/bin/app.py")
    .rename("/workspace/app.py", "/workspace/main.py")
    .remove("/tmp/cache", recursive=True, force=True)
)
// Note: the TypeScript builder exposes makeDir; makeSymlink, rename, and
// remove are not available yet — use the Python SDK, CLI, or HTTP API.
builder.makeDir("/workspace/logs");
nullspace template build \
  --from-python-image 3.12 \
  --make-dir /workspace/logs \
  --make-symlink-target /workspace/app.py --make-symlink-path /usr/local/bin/app.py \
  --rename-src /workspace/app.py --rename-dst /workspace/main.py \
  --remove /tmp/cache --remove-recursive --remove-force \
  --name agent-template
# Each file operation is a "steps" entry. Builds stream over SSE — 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": "agent-template",
    "base_image": "python:3.12",
    "steps": [
      {"action": "make_dir", "path": "/workspace/logs"},
      {"action": "make_symlink", "target": "/workspace/app.py", "path": "/usr/local/bin/app.py"},
      {"action": "rename", "src": "/workspace/app.py", "dst": "/workspace/main.py"},
      {"action": "remove", "path": "/tmp/cache", "recursive": true, "force": true}
    ]
  }'

Render before build

print(builder.to_json())
print(builder.to_dockerfile())
Rendered templates are useful for review, CI diffs, and nullspace template render json|dockerfile parity.
nullspace template render json --from-python-image 3.12 --apt-install git --name agent-template
nullspace template render dockerfile --from-python-image 3.12 --apt-install git --name agent-template
Build resource and cache options live on the build call:
build = Template.build(
    builder,
    name="agent-template",
    tags=["stable"],
    cpu_count=4,
    memory_mb=2048,
    skip_cache=True,
)
The same options are available on Template.build_in_background() and builder.to_json(...).