Templates
Shardflux templates: versioned images that workspaces start from, with environment, inputs, start commands and services, and python-node-browser.
What a template is
A template is the image a workspace starts from: an operating system, languages, packages and files, plus the settings every workspace of it gets when it opens. You name it by its slug when you use a key:
workspace('customer-42/main', { template: 'default' });The default template
default names the platform default template, python-node-browser: use it when you have no preference. A workspace
opened with default records python-node-browser as its template, and it starts as fast as any other. An
organization that builds its own template with the slug default replaces it for its own workspaces.
Templates come in two kinds:
| Kind | Who publishes it | Examples |
|---|---|---|
| Platform template | Shardflux. Every project can open it. | python-node-browser (category stack, also named default), ubuntu-24.04 (category os) |
| Organization template | You, by building one or saving a workspace as one | acme-dev |
Versions
A template has numbered, immutable versions. When a key is opened for the first time, the slug resolves to the template's latest published version, and the workspace keeps that version for its whole life. Publishing a new version changes what new keys get; existing workspaces keep theirs, and pick up the template's immutable paths from the newest published version. Fork keeps the source's version, and reset returns to it.
Platform security fixes reach every workspace on their own. Your templates follow their base template onto the release that carries the fix, as a new published version, and existing workspaces move onto it at their next resume, keeping their files, packages and work. Other platform releases reach new workspaces, so a resume keeps its memory.
workspace.template (TypeScript) and shard ws get <key> show the slug and version a workspace runs.
Public templates
List what your project can open, and look one up:
shard templates list
shard templates get python-node-browserconst page = await cloud.templates.list();
for (const t of page.data) console.log(t.slug);
const detail = await cloud.templates.get('python-node-browser');print([t["slug"] for t in sf.templates.list(owner="platform").data])
detail = sf.templates.get("python-node-browser")python-node-browser is the template these docs use. It is Ubuntu 24.04 with Python 3.12 (pip, venv), Node.js 24
(npm), git, curl, build-essential and a headless Chromium browser. A workspace of it sees up to 8 GiB of memory from
boot and holds what its commands use, 1 GiB while it is idle; caps.memory_mib sets a lower maximum. ubuntu-24.04 is a minimal base (bash, apt, sudo, curl and
certificates) to build your own templates on.
Look inside a version without opening a workspace:
shard templates get acme-dev # versions, and which one open resolves to
shard templates files acme-dev 3 /home/user # one directory of version 3
shard templates diff acme-dev --from 2 --to 3 # what changed between two versionsVersions without a file list answer 409 (details.reason: file_list_unavailable).
Settings: environment, inputs, start commands and services
A template version built from a recipe (see Build a template) or saved from a workspace carries settings that apply every time one of its workspaces opens:
| Setting | What it does | Limits |
|---|---|---|
env |
Environment variables for every command, terminal, start command and service | 128 names; values up to 4,096 bytes; names starting with SHARDFLUX_ are reserved |
inputs |
Values supplied when a workspace is opened: text (put into the environment) or secret (binds the stored secret of the same name) |
32 inputs; text values up to 4,096 bytes, no CR, LF or NUL |
start |
Commands run at create (first boot of a new workspace), boot (a later cold boot) or resume |
32 commands; timeout_seconds 1 to 1800 (default 300), 3,600 seconds in total |
services |
Long-running processes kept up by the workspace, with restart (always, on_failure, never) and a readiness check (port or command) |
16 services; ready_timeout_seconds 1 to 600 (default 60) |
defaults |
Default lifetime, idle_timeout_seconds for sessions, an egress ceiling (internet, allowlist, none) and resource limits |
Environment precedence, lowest first: the workspace's base environment, the template's env, the workspace's text
inputs, then the command's own env.
Pass inputs when you open a key:
const ws = await cloud.workspaces.open({ key: 'customer-42/main', template: 'acme-dev', inputs: { PROJECT_NAME: 'acme' } });
console.log(await ws.inputs(), ws.startup);ws = sf.open(key="customer-42/main", template="acme-dev", inputs={"PROJECT_NAME": "acme"})
print(ws.inputs(), ws.startup)shard ws open customer-42/main --template acme-dev --input PROJECT_NAME=acmeOn a new key, each input takes the given value, else its declared default. On an existing key, inputs replaces them
all; leaving it out keeps them. A missing required input is 422 (details.reason: input_required), an undeclared one
input_unknown, and a value that breaks the rules input_invalid.
Start commands and services at open
open waits for the start commands and services: a workspace is ready when they have run and its services report
ready. workspace.startup shows the progress (pending, running, ready or failed).
- Which commands run. A new workspace (and a reset one) runs its
createandbootcommands, in the declared order. A later cold boot runsbootcommands. A resume or fork restores memory, so it runsresumecommands, and the services are still running. - When a step fails. The open fails with
startup_failed(retryable). The workspace keeps running so you can look at it:workspace.startupnames the failed step, its exit code and the last 4 KiB of its output. The next open runs the failed step again. - Changed inputs apply to later commands, not to services that are already running.
Layered disks
Template versions that support it give workspaces a layered disk: the template is a read-only layer, and the workspace's own changes are a separate layer. That separation makes reset, saving a workspace as a template and listing a workspace's changes against its template possible:
const page = await workspace.changes({ pathPrefix: '/home/user', summary: true }); // added | modified | metadata | deleted | replacedWorkspaces created from older versions are legacy (one disk) and answer those calls with 409
(details.reason: legacy_disk_layout). The layout is chosen at the first open and never changes;
workspace.diskLayout (TypeScript) and ws.disk_layout (Python) report it.
Immutable paths
Ship your tooling once and every workspace runs the current release. A template declares the directories that hold it, such as a CLI, an SDK or the instructions your agent reads, as immutable. Every workspace of the template shows those directories from the template's newest published version, and everything else on its disk stays its own:
| Part of the disk | What it holds | When it changes |
|---|---|---|
| Immutable paths | The directories the template declares, read-only | At each start and resume, to the template's newest published version |
| Everything else | The workspace's files, packages and work, created from its version at the first open | Only when the workspace changes it |
Declare them in template.yaml with immutable (see Build a template):
# acme/template.yaml
base: ubuntu-24.04@2
immutable: [/opt/acme]
build:
files:
- { from: ./acme, to: /opt/acme } # bin/acme, lib/ and AGENTS.md
steps:
- { name: link, run: ln -s /opt/acme/bin/acme /usr/local/bin/acme }shard templates build acme/template.yaml --slug acme-dev --publish --waitWith /opt/acme declared since version 3, a workspace created from version 3 keeps its own files, packages and
work, and runs the acme CLI and reads the AGENTS.md of version 5 once version 5 is published:
const ws = await cloud.workspaces.open({ key: 'customer-42/main', template: 'acme-dev' });
console.log(ws.template.version, ws.template.immutable_version); // 3 5- Rollout is publishing. A workspace shows the newest published version at its next start or resume: an open that boots it, a reset, or a resume from suspend. A running workspace never sees its immutable paths change, so a task never has its tooling replaced half way. Lifecycle has each transition.
- Rollback is archiving. Archive the newest version (
shard templates archive acme-dev@5, or the API) and the previous one is current again; workspaces return to it at their next start or resume. - Read-only, by design. One release serves every workspace, so nothing inside a workspace changes it. A write
under an immutable path fails with
Read-only file system, and the file API answers409 conflict(details.reason: read_only_path). An agent that needs a variant copies the tool into its own files and changes the copy:cp -r /opt/acme ~/acme. - The list carries forward. A version whose
template.yamlleavesimmutableout keeps the current version's list. A new version may add paths and keeps every path the current version declares (dropping one is422 immutable_path_removed), so a forgotten line never takes tooling away from every workspace. - Saving keeps tooling current. Save as template takes the immutable paths from the template's newest published version, never from the workspace being saved. In a draft and its test instances the immutable paths are ordinary, writable directories: change the tooling there, try it, and publishing the draft publishes it. A version test instance shows that version's own files, so you can try an unpublished release before you publish it.
- Which workspaces follow. Workspaces opened on a version that declares immutable paths, and their forks. Workspaces created from an earlier version keep their own copy of those directories.
Your organization's templates declare immutable paths, and the API reports them on three objects:
| Object | Field | Meaning |
|---|---|---|
| Workspace | template.immutable_version |
The template version whose immutable paths the workspace shows; null when it shows none |
| Template version | immutable |
{ paths, bytes }: the paths the version declares and the size of their image; null when it declares none |
| Template build | immutable_paths |
The paths the version it produces declares, written in the recipe or carried forward; empty for none |
shard ws get <key> shows a workspace's immutable_version and shard templates get <slug> each version's paths
(CLI 0.8.0+). Counts and sizes are in Pricing and limits.
Ways to make your own template
| Way | When | How |
|---|---|---|
Build from template.yaml |
Reproducible templates kept in your repository | Build a template |
| Save a workspace as a template | You set a workspace up by hand or with an agent | workspace.saveAsTemplate({ templateSlug }), ws.save_as_template(slug), shard ws save-as-template <key> --template <slug> |
| Draft (dev mode) | Iterate on a template in a live workspace, test copies, then publish | cloud.templates.draft(slug), sf.templates.draft(slug), shard templates draft ... |
Saving and drafts need a layered workspace. Organization templates are stored once per distinct layer, and their storage counts toward your plan's retained storage (see Pricing and limits).