# 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 open a key:

```ts
await cloud.workspaces.open({ key: 'customer-42/main', template: 'python-node-browser' });
```

Templates come in two kinds:

| Kind | Who publishes it | Examples |
| --- | --- | --- |
| Platform template | Shardflux. Every project can open it. | `python-node-browser` (category `stack`), `ubuntu-24.04` (category `os`) |
| Organization template | You, by [building one](https://docs.shardflux.dev/guides/build-a-template.md) 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. [Fork](https://docs.shardflux.dev/concepts/lifecycle.md#fork-a-workspace) keeps the
source's version, and [reset](https://docs.shardflux.dev/concepts/lifecycle.md#reset-a-workspace) returns to it.

`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:

```sh
shard templates list
shard templates get python-node-browser
```

```ts
const page = await cloud.templates.list();
for (const t of page.data) console.log(t.slug);
const detail = await cloud.templates.get('python-node-browser');
```

```python
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 gets
2,048 MiB of memory unless you set `caps.memory_mib`. `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:

```sh
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 versions
```

Versions published before file lists existed 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](https://docs.shardflux.dev/guides/build-a-template.md)) 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:

```ts
const ws = await cloud.workspaces.open({ key: 'customer-42/main', template: 'acme-dev', inputs: { PROJECT_NAME: 'acme' } });
console.log(await ws.inputs(), ws.startup);
```

```python
ws = sf.open(key="customer-42/main", template="acme-dev", inputs={"PROJECT_NAME": "acme"})
print(ws.inputs(), ws.startup)
```

```sh
shard ws open customer-42/main --template acme-dev --input PROJECT_NAME=acme
```

On 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](https://docs.shardflux.dev/concepts/lifecycle.md#reset-a-workspace) one) runs its
  `create` and `boot` commands, in the declared order. A later cold boot runs `boot` commands. A resume or fork restores
  memory, so it runs `resume` commands, 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.startup` names 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 and legacy workspaces

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](https://docs.shardflux.dev/concepts/lifecycle.md#reset-a-workspace),
saving a workspace as a template and listing a workspace's changes against its template possible:

```ts
const page = await workspace.changes({ pathPrefix: '/home/user', summary: true });   // added | modified | metadata | deleted | replaced
```

Workspaces 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.

## Ways to make your own template

| Way | When | How |
| --- | --- | --- |
| Build from `template.yaml` | Reproducible templates kept in your repository | [Build a template](https://docs.shardflux.dev/guides/build-a-template.md) |
| 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](https://docs.shardflux.dev/limits.md)).
