temperDocs
Menu

Concepts

Templates

Install system packages and tools into your environments with templates, built from steps or from a Dockerfile.

Every environment starts from the same runtime: Debian 13 with systemd, Python 3 (with venv, without pip), curl and git. A template adds what your agent needs on top of it: system packages, language runtimes, a browser, CLI tools, configuration files. You build a template once, as an immutable version, and environments created from that version all have the same software installed.

Templates and agent packages do different jobs. The agent package is your agent itself, mounted at /opt/agent. The template is the system around it. Each has its own versions, and you can ship one without rebuilding the other.

Install system software through templates

Everything outside /workspace is rebuilt whenever the VM starts (see what survives what). If your agent runs apt-get install at run time, the package is gone after the VM powers off or is replaced. So:

  • System packages and global tools belong in a template.
  • Per-user data, and things like a Python virtual environment or node_modules that the agent creates for a user, belong under /workspace, where they are kept.

Build steps

A template version is built from a list of steps, run in order. Each step is an object with one key:

StepWhat it does
{"run": "..."}Runs a shell command as root with /bin/sh -c: apt-get install, pip install, curl and so on.
{"env": {"NAME": "value"}}Sets environment variables for later steps and for every environment using the template.
{"workdir": "/srv/app"}Sets the directory later run steps (and start) run in. It is created if it is missing.
{"copy": {"path": "...", "content": "...", "encoding": "utf8", "mode": "0644"}}Writes a file. Use "encoding": "base64" for binary content.

A version has at most 64 steps, and all copy contents together are limited to 4 MiB. Download anything bigger in a run step. Files written by copy are owned by root and readable by everyone; add a run step with chown agent:agent ... if your agent has to write to them.

Template variables have the lowest priority: the agent package's /opt/agent/env and the environment's config override them. They are not expanded when they are loaded, so write out a full PATH rather than $PATH; the runtime's default is /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin.

start is optional: the command an environment runs when it has no agent package, as a string (run with /bin/sh -c) or an argv array, in the last workdir. With an agent package, /opt/agent/bin/start runs instead. With neither, the environment simply waits for exec calls.

import { Temper } from "@temper-hq/sdk";

const temper = new Temper();

await temper.templates.createVersion("py-data", "1", {
  steps: [
    { run: "apt-get update && apt-get install -y --no-install-recommends ffmpeg" },
    { run: "python3 -m venv /opt/py && /opt/py/bin/pip install pandas==2.2.3" },
    { env: { PATH: "/opt/py/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" } },
  ],
});
const version = await temper.templates.waitForVersion("py-data", "1");
console.log(version.state); // "ready"
from temper_hq import Temper

with Temper() as temper:
    temper.templates.create_version(
        "py-data",
        "1",
        steps=[
            {"run": "apt-get update && apt-get install -y --no-install-recommends ffmpeg"},
            {"run": "python3 -m venv /opt/py && /opt/py/bin/pip install pandas==2.2.3"},
            {"env": {"PATH": "/opt/py/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}},
        ],
    )
    version = temper.templates.wait_for_version("py-data", "1")
    print(version.state)  # "ready"
client, err := temper.New()
if err != nil {
	log.Fatal(err)
}
_, err = client.Templates.CreateVersion(ctx, "py-data", "1", temper.CreateTemplateVersionParams{
	Steps: []temper.TemplateStep{
		temper.RunStep("apt-get update && apt-get install -y --no-install-recommends ffmpeg"),
		temper.RunStep("python3 -m venv /opt/py && /opt/py/bin/pip install pandas==2.2.3"),
		temper.EnvStep(map[string]string{"PATH": "/opt/py/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}),
	},
})
if err != nil {
	log.Fatal(err)
}
version, err := client.Templates.WaitForVersion(ctx, "py-data", "1", nil)

To build from a Dockerfile instead of writing steps, see Build a template from a Dockerfile.

How a build runs

createVersion returns at once with state: "building". Temper then runs the steps in a temporary environment that belongs to you:

  • run steps execute as root inside the runtime, with the same isolation as your agent. The files they leave behind become the template's layer.
  • Egress during the build is limited to common package registries (Debian, PyPI, npm, crates.io and GitHub releases) plus your developer-level egress allowlist. Add hosts there if a step downloads from somewhere else.
  • A step can run for up to 30 minutes, and a whole build for up to an hour. The first step that exits with a non-zero status fails the build.
  • While the build runs, the temporary environment appears in your environment list with end_user_id template-build:<name>:<version>. It is destroyed when the build ends. Its CPU time and egress count toward your usage.

The version then becomes ready or failed. getVersion returns the error and the last 64 KiB of the build log, with each step's command and output. waitForVersion (Python wait_for_version, Go WaitForVersion) polls until the build finishes and raises template_build_failed with the end of the log if it fails.

A version whose base, steps and start match one you have already built reuses that build instead of running the steps again.

Versions

  • Template names are 1–64 lowercase letters, digits and -. Versions are up to 64 letters, digits, ., _ and -.
  • A version can't change. Creating one that exists returns 409 version_exists, so build changes as a new version.
  • from builds on top of another ready version of yours ("base:3") instead of the plain runtime, so several templates can share a common base. A base that is still building or failed returns 409 template_not_ready.
  • deleteVersion removes a version. A version that is still building returns 409 template_building; one used by an environment that hasn't been destroyed, or by another version as its base, returns 409 template_in_use.

Use a template

Pass template as name:version when you create an environment. The version must be ready; otherwise the call returns 409 template_not_ready.

const env = await temper.environments.create({ end_user_id: "user_42", template: "py-data:1" });
console.log(env.template); // "py-data:1"
env = temper.environments.create(end_user_id="user_42", template="py-data:1")
print(env.template)  # "py-data:1"
env, err := client.Environments.Create(ctx, temper.CreateEnvironmentParams{EndUserID: "user_42", Template: "py-data:1"})

An environment keeps the template version it was created with; publishing a new version doesn't change existing environments. Environments created without template use the plain runtime, and their template is null.

Limits

LimitValue
Steps per version64
run command16 KiB
copy contents, all steps together4 MiB
One step30 minutes
One build1 hour
Build log keptlast 64 KiB