Template reference

.ciresk/dev.yaml tells Ciresk dev what a session runs: the toolchains, the steps that prepare the workspace, the services and their ports, the browser, what may leave the session and how long it lives when nobody uses it. This page is the reference for every field the parser accepts.

Example

Adapted from the template Ciresk runs its own monorepo with. Replace registry.example.com with the registry your images come from.

.ciresk/dev.yaml
version: 1
templates:
  app:
    description: the web app on postgres 17 with pgvector; chromium on demand
    toolchains: {node: "24", pnpm: "11.1.3"}
    workspace:
      prepare:
        - name: node
          run: [pnpm, install, --frozen-lockfile, --prefer-offline]
          memory: 3Gi
          cpus: 2.0
          inputs: [pnpm-lock.yaml, pnpm-workspace.yaml, "**/package.json"]
          shared: true
    services:
      postgres:
        image: registry.example.com/mirror/pgvector/pgvector:0.8.6-pg17@sha256:cf134a767f474095eeba57e0117be8e568e011a63f33fbf252f14c9b760f8e6f
        command: [postgres, -c, shared_buffers=64MB, -c, max_connections=100]
        env: {POSTGRES_USER: app, POSTGRES_PASSWORD: app, POSTGRES_DB: app, PGDATA: /var/lib/postgresql/data/pgdata}
        ports: [{name: postgres, port: 55437, target: 5432, kind: tcp}]
        readiness: {exec: [pg_isready, -h, 127.0.0.1, -U, app, -d, app]}
        volumes: [{name: postgres, path: /var/lib/postgresql/data}]
        memory: 256Mi
        cpus: 0.25
      web:
        image: workspace
        after: [node:completed]
        start: on_demand
        idle_stop: 30m
        command: [pnpm, dev]
        env: {NEXT_TELEMETRY_DISABLED: "1", DATABASE_URL: "postgres://app:[email protected]:5432/app"}
        ports: [{name: web, port: 3000, kind: http}]
        readiness: {http: {port: 3000, path: /}}
        memory: 1Gi
        memory_ceiling: 1792Mi
        memory_max: 2560Mi
        cpus: 1.0
        cache_dirs: [.next, node_modules/.cache]
        cache_max: 4Gi
        heal: {restart_on: [memory_ceiling, memory_growth, not_answering], growth_factor: 3}
        serves: static
        diagnostics: [{next_mcp: 3000}]
    browser:
      image: registry.example.com/mirror/playwright/mcp:v0.0.83@sha256:690763cbb437ee7233f538a953bb5390bf03791eb5c965b471e59d17a6132fcb
      start_url: "{origin:3000}"
    egress: []
    registry_cache: true
    idle: 60m
    disk: 32Gi
    shell: {memory: 6Gi, cpus: 4.0}

ciresk dev up app installs the node packages once, starts postgres, and starts web the first time something opens http://app.localhost:3000. web declares serves: static because this app writes no generation marker, so ciresk dev wait --gen does not wait for it (the warning workspace_serves_static).

Rules for the whole file

RuleWhat it means
Location.ciresk/dev.yaml at the root of the repository. ciresk dev sends its bytes and their digest with every up, so the session runs exactly the file in your worktree.
SizeAt most 256 KiB.
versionRequired; 1 is the only version.
templatesRequired; a map of named templates. One file holds as many as you need, for example one per product of a monorepo.
NamesTemplate, service, prepare step, port, volume, file and identity names match ^[a-z][a-z0-9-]{0,30}$.
Unknown fields are errorsA typo is refused with its path, never silently ignored.
SizesBytes as a whole number, or a whole number with Ki, Mi, Gi, Ti (binary) or k, M, G, T (decimal): 768Mi, 8Gi. No fractions.
DurationsA whole number with ms, s, m or h: 500ms, 30m, 2h.
CPUsA decimal above 0 and at most 256: 0.25, 1.0.
Validationciresk dev up checks the template on your machine before it sends it, and the control plane checks it again with your organisation's policy. Any error refuses the session with exit 2 template_invalid and the list of findings, each with its path, code and, where one exists, the command that fixes it. Warnings are reported and never refuse.

Which template ciresk dev up starts

ciresk dev up <name> starts the named template. Without a name:

  1. A file with one template starts that one.
  2. Otherwise the template whose workspace.prefetch paths hold the directory you run from wins; prefetch: [services/api/**] claims services/api and everything below it, and the deepest match wins.
  3. When no template claims the directory (at the repository root, for example), or two claim it equally deep, the template named like the repository wins: the name of the repository's folder or the last part of its remote URL.
  4. Otherwise up refuses with no_template and lists the names.

A template whose description starts with blocked: is skipped in step 2. When only blocked templates claim the directory, the template named like the repository starts instead, and up says so; when there is none, the blocked one is picked and refused. Starting a blocked template, by name or this way, fails with exit 2 template_invalid and the finding template_blocked. Write blocked: <reason>; next: <what unblocks it> to keep a template in the file before it can run; the text after next: is shown as the next step.

Each worktree gets its own session. When you change .ciresk/dev.yaml under a running session, the session keeps the template it started with and ciresk dev status says the file changed; the next ciresk dev up starts the session again on the new file. sessions.md covers the rest of a session's life.

ciresk dev init

Terminal
ciresk dev init --from docker-compose.yml --name app
ciresk dev init --from .devcontainer/devcontainer.json --write

init translates a compose file or a devcontainer into a template and prints it with what it could not translate. Without --from it looks for .devcontainer/devcontainer.json, compose.yaml and docker-compose.yml, in that order. --name names the template (default: the repository's folder), --app <dir> adds an app of a monorepo with its own install step and dev server, --write merges the result into .ciresk/dev.yaml, and --force replaces a template of the same name. Any error finding exits 2 with template_invalid. The findings you will meet most:

FindingMeaning
image_unpinnedThe compose image has no digest. init never asks a registry; pin it yourself.
build_not_supportedThe service is built by compose. A session never builds an image: publish it to a registry and pin it.
host_mountA bind mount outside the repository has no equivalent in a session.
secret_in_envA value looks like a real credential. It is not written to the template.
port_collisionTwo services listen on the same port, which one session cannot hold.
resources_estimatedThe compose file names no resources; init wrote estimates. Check them.

Template fields

FieldTypeDefaultWhat it does
descriptionstringemptyOne line shown in listings; blocked: marks the template as not runnable (above).
toolchainsmap of name to versionnoneThe tools installed in every image: workspace container.
workspaceobjectrequiredWhere the code is mounted and the steps that prepare it.
servicesmap of name to servicenoneThe containers of the session.
browserobjectnoneA Chromium the session's agents drive, on the session's own pages.
egresslist of rows[]The destinations outside the session it may reach. Empty means none.
registry_cachebooleanfalseGives the session the worker's read-only package registry cache.
idleduration60mHow long the session lives without activity. 5m to 8h.
disksizerequiredThe session's disk quota, above 0.
dataobjectnoneThe volumes ciresk dev reset restores.
shellobjectsee belowThe SSH and exec containers every session gets.
ideobjectsee belowEditor settings for those containers.
queueobjectfrom the session's CPUsHow many operations run at once.
parameterslist of parametersnoneTyped inputs a session is started with (below).
presetslist of presetsnoneNamed sets of parameter values.
oauthobjectnoneRedirect URIs and identity provider hosts for an app's sign-in. Not available yet: the parser accepts it so a template can carry it, and it has no effect. Fields callback_path (string, required), redirect_uris, idp_hosts and start_paths (lists of strings, default empty).

Toolchains

Code
toolchains: {rust: "1.97.1", node: "24", pnpm: "11.1.3"}
RuleWhat it means
Namesbun, cargo:cargo-nextest, deno, go, java, node, pnpm, python, ruby, rust, terraform, uv. Anything else is refused with toolchain_refused; install other tools in a prepare step.
VersionsA plain version, 1 to 64 characters of letters, digits, ., + and -: "24", "1.97.1".
WhereInstalled on the worker and mounted read-only into every image: workspace container, which runs the session base image (a Linux image with watchexec and a C toolchain, user 1000, working directory /workspace).
RustWith a rust toolchain every workspace container gets its own CARGO_HOME on a session volume, so cargo install works. Targets and components named in the repository's rust-toolchain.toml are added to the toolchain.

Workspace

FieldTypeDefaultWhat it does
mountabsolute path/workspaceWhere the code is mounted in every container that has access to it.
writestage_rename or in_placestage_renameHow a synced file is written. in_place keeps the file's inode, for watchers bound to inodes such as node --watch.
preparelist of stepsnoneCommands that run to completion before the services that wait for them.
materialisefull or lazyfulllazy fetches file content on first read instead of before the start. It takes effect only when ciresk dev up --lazy also asks for it.
prefetchlist of globsnonePaths fetched first in a lazy session, and the paths that pick this template for a bare ciresk dev up.

Prepare steps

Each step is a container over the workspace that runs once. Services wait for it with after: [<step>:completed], so a service waits only for the steps it needs.

FieldTypeDefaultWhat it does
namenamerequiredThe step's name, unique among steps and services.
runlist of stringsrequiredThe command, as an argument list. Not empty.
afterlist of step namesnoneOther steps that must complete first.
memorysizerequiredMemory the step reserves, above 0.
cpusdecimalrequiredCPU share the step reserves, above 0.
inputslist of globsnoneWorkspace files the step reads. When a synced change touches one, the step runs again.
sharedbooleanfalseReuses the step's result across sessions whose inputs match.
layer_inputslist of globsnoneOnly with shared: more files the shared result is built from and keyed by, without re-running the step when they change, such as a Rust build's sources.

Globs are relative to the workspace and use **, * and ?; .., braces and character classes are refused (bad_glob).

A shared step must list every file it reads in inputs (shared_without_inputs) and wait for no other step (shared_after). The first session of a set of input files on a worker runs the step and, beside it, builds a layer from exactly those files. A later session on that worker whose tracked input files match byte for byte mounts the layer read-only and skips the step: a pnpm install of an unchanged lockfile takes seconds instead of a minute. A Rust build step can be shared too: list Cargo.lock and the manifests in inputs and the sources in layer_inputs; a later session's files that match the build's are marked older than it, so cargo finds the whole build fresh and compiles only the crates whose files differ. Layers stay within your organisation, and a lazy session always runs the step.

Services

Code
services:
  api:
    image: workspace
    after: [rust:completed, postgres]
    command: [watchexec, --restart, --exts=rs, --, cargo, run]
    ports: [{name: api, port: 8080, kind: http}]
    readiness: {http: {port: 8080, path: /ready, status: 200}}
    memory: 384Mi
    cpus: 0.5

Every service and step of a session shares one network namespace: services reach each other on 127.0.0.1 and the in-pod port.

FieldTypeDefaultWhat it does
imagestringrequiredworkspace (the session base image with the template's toolchains), or an image pinned by digest.
pull{secret, user}nonePulls a pinned image from your own registry with the organisation's secret secret and the registry user user. See Images.
workspacenone, read_only or read_writeread_write for image: workspace, else noneThe service's access to the workspace mount.
commandlist of stringsthe image's ownThe command, as an argument list. Required for image: workspace.
envmap of string to stringnoneEnvironment variables. Values may hold placeholders.
afterlistnone<name> (or <name>:ready) waits until that service is ready; <name>:completed waits until that step or run: once service has succeeded.
runservice or onceserviceonce runs to completion, like a prepare step with its own image (a migration, a bucket setup).
restarton-failure or neveron-failure for services, never for onceon-failure restarts a service that fails, at most 3 times.
starteager or on_demandeageron_demand starts the service the first time something reaches its port, or ciresk dev exec or ciresk dev restart names it.
idle_stopdurationnoneFor an on_demand service: stop it after this long without a connection to its ports. Above 0.
portslistnoneThe ports it listens on.
readinessobjectnoneExactly one of tcp: <port>, http: {port, path, status} or exec: [argv]. An http probe without status accepts any answer below 500. Without a probe the service is ready once it runs.
memorysizerequiredMemory the service reserves; the session is placed on a worker by the sum. Above 0.
memory_ceilingsize1.5 x memory, at most memory_maxWhere the kernel starts to throttle the service, and the memory_ceiling heal trigger.
memory_maxsize2 x memoryThe hard limit; above it the process is killed. memory at most memory_ceiling at most memory_max, or memory_order.
cpusdecimalrequiredThe service's CPU share, above 0. A share, not a cap: a process still sees every core of the host, so limit a build's parallelism yourself (cargo build -j 4).
shmsizethe runtime'sSize of /dev/shm. Above 0 when set.
volumeslist of {name, path}noneNamed volumes at absolute paths. They live as long as the session; services that name the same volume share it.
fileslist of {name, path, content}noneFiles written for the service at absolute paths outside the workspace: a config file for an upstream image.
cache_dirslist of workspace pathsnoneDirectories the service fills as a cache (.next, target).
cache_maxsizenoneWhen cache_dirs grow past it, the service is reported degraded. Needs cache_dirs.
serveshmr, restart, diagnostics or staticrestart for image: workspace, else staticHow the service proves it runs your latest change.
hmr_probeobjectnoneMeasures hot reload for serves: hmr.
diagnosticslistnoneWhere compile and type errors come from.
healobjectnoneRestarts the service when a measured condition holds.
shareper_session or per_workerper_sessionper_worker (one server per worker shared by many sessions) is not available yet and is refused with share_pending_owner.
sealedbooleanfalseSeals the container: no exec, SSH or log stream into it, and no other container may share its volumes. Needs a pinned image.

Images

An image other than workspace is named by its registry host and pinned by digest: registry.example.com/team/api:1.4@sha256:<64 hex>. A short name such as postgres:17 is refused with unqualified_image, an image without a digest with unpinned_image; both findings name the fully qualified form to pin. A digest of all zeros is a placeholder for an image not released yet and is refused with unreleased_image.

An image on Ciresk's own registry (where the public images a template uses are mirrored under the same tag and digest) is pulled as it always was. An image on any other registry is yours: GHCR, Docker Hub, ECR, Artifactory, Harbor or any OCI registry. The worker pulls it before each start of its service, outside the session, through the session's own egress:

  • The registry's host, and every host it redirects to, must be in the template's egress, in your organisation's egress ceiling when it has one, and allowed by the installation's egress.yaml, as for any other egress. A host that is not is refused with image_registry_not_allowed; ciresk dev egress names each refused redirect. Common sets: GHCR ghcr.io and pkg-containers.githubusercontent.com; Docker Hub registry-1.docker.io, auth.docker.io and production.cloudflare.docker.com.
  • A private image names its credential with pull: {secret: <name>, user: <registry user>}. The secret is an organisation secret (ciresk dev secrets set <name> --host <registry> --repo <repository>, with a read-only token as its value); dev up refuses a secret the session is not granted with pull_secret_not_granted. Bind the secret to the host the registry serves from: registry-1.docker.io for Docker Hub, which also sends the credential to auth.docker.io, so bind both there. The value never enters the session, nor the pull itself: the pull holds the session's placeholder, and the worker's proxy puts the value in the request only when it goes to a host the secret is bound to, so a registry that sends the credential to any other host gets the placeholder. The secret keeps the default header, authorization (pull_secret_header otherwise); a token endpoint on a host the secret is not bound to fails the service with pull_realm_not_bound, naming the host to add.
Code
services:
  api:
    image: ghcr.io/acme/api:1.4@sha256:<64 hex>
    pull: {secret: ghcr, user: acme-bot}
    memory: 256Mi
    cpus: 0.5
egress:
  - {host: ghcr.io, ports: [443], purpose: the api image}
  - {host: pkg-containers.githubusercontent.com, ports: [443], purpose: the api image's layers}

Environment

Values are plain strings; quote numbers and booleans (PORT: "3000"). Four placeholders are filled in for each session:

PlaceholderValue
{label}The session's label: the template name in your main worktree (app), the template name and the worktree's folder in another (app-feat-x), with -2, -3 added when it is taken on your machine.
{origin:<port>}http://<label>.localhost:<port>. The port must be an http port of the template whose in-pod target is the same port.
{random:<name>}24 random letters and digits, the same for one name within a session, never logged. The name matches ^[a-z][a-z0-9_-]{0,30}$.
{param:<name>}The session's value of a parameter the template declares (see "Parameters and presets"). Only in env.

Any other {...} group is refused with unknown_placeholder.

Ciresk sets CIRESK_LOGIN_KEY, CIRESK_LOGIN_TTL, CIRESK_SESSION_EPOCH and the model variables ANTHROPIC_BASE_URL, OPENAI_BASE_URL, ANTHROPIC_API_KEY, OPENAI_API_KEY, ANTHROPIC_MODEL, NODE_EXTRA_CA_CERTS, SSL_CERT_FILE and CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, REQUESTS_CA_BUNDLE and every variable whose name starts with CIRESK_SECRET_ (in any letter case) itself; a template that sets one is refused with reserved_env.

Placeholders are filled in only in a service's env, in the browser's start_url, and in a test identity's http login or revoke call (its URL, headers and body). {param:<name>} is refused in a URL (parameter_env_only). Anywhere else (a command, a file's content, a probe) the text is passed as it is.

The template is stored with every session and readable by anyone who can read the repository. Never put a real credential in env: use local stand-ins (POSTGRES_PASSWORD: app) or {random:<name>}. A session holds no reusable secret.

With registry_cache: true, every workspace container gets npm_config_registry and pnpm_config_registry pointing at the cache, unless the template sets them.

On-demand services

A request through the service's URL, a local port or an SSH forward starts an on_demand service when it reaches a port the service owns, and waits for it to be ready. A service owns a port through its tcp or http readiness probe or its hmr_probe. With only an exec probe it owns none, so a request never starts it (the warning on_demand_owns_no_port).

An on_demand service with idle_stop stops after that long with no connection to its ports; the next request starts it again. An eager service cannot wait for an on-demand one (after_on_demand), and a run: once service is never on demand (once_on_demand).

Serving the latest change

Every sync of your worktree is a numbered generation. serves tells Ciresk how a service shows which generation it runs, so ciresk dev status can show it and ciresk dev wait --gen <n> can wait for it. Only image: workspace services may use anything but static.

ValueHow the service proves it
restartThe process writes the generation to /run/ciresk/diag/<service>.gen and then becomes ready. Copy <mount>/.ciresk/generation there at the start of each rebuild.
hmrThe hmr_probe writes a nonce to a workspace file and fetches it back through the dev server. Needs hmr_probe.
diagnosticsA type checker in watch mode finished a cycle over the generation. Needs a tsc_json diagnostics source.
staticAlways current: databases, caches and upstream images.

hmr_probe fields: file (a workspace path the probe writes), content (holds {nonce} exactly once), path (the URL path that returns the file, starting with /), port (an http port of this service), slo (default 2s) and every (default 1m, at least slo).

diagnostics is a list; each entry is exactly one of:

SourceWhat it reads
cargo_json: <file>cargo build --message-format json output, in a file under /run/ciresk/diag/. image: workspace only.
tsc_json: <file>TypeScript errors written by ciresk-tsc-json (in the session base image), in a file under /run/ciresk/diag/. image: workspace only.
next_mcp: <port>Next.js's own MCP endpoint on an http port of this service.

ciresk dev diagnostics returns them as data.

Heal

FieldTypeDefaultWhat it does
restart_onlistnoneRestarts the service when one of these holds: memory_ceiling (at memory_ceiling for 30 s), memory_growth (more than growth_factor times its baseline for 2 minutes), hmr_failing (needs hmr_probe), not_answering (needs readiness or hmr_probe).
growth_factordecimal3The memory_growth threshold, at least 1.5.

Without heal, these conditions only mark the service degraded in ciresk dev status. A ready service that fails its readiness probe three times in a row is degraded either way.

Ports and URLs

Code
ports:
  - {name: web, port: 3000, kind: http}
  - {name: postgres, port: 55437, target: 5432, kind: tcp}
FieldTypeDefaultWhat it does
namenamerequiredUnique across the whole template.
port1 to 65535requiredThe port of the URL for an http port; for a tcp port, the number the local port is derived from (below).
target1 to 65535portThe port the service listens on inside the session.
kindhttp, tcp or internalrequiredHow the port is reached.
KindReached at
httphttp://<label>.localhost:<port> from your browser, curl and your programs. The per-machine daemon serves every session on the same port and tells them apart by the Host header, so two worktrees on port 3000 are two origins. While a service starts, the URL answers 503 with Retry-After.
tcpA loopback port of its own for each session, between 20000 and 29999, derived from the worktree and port, so it stays the same on every run. ciresk dev status prints it with its target; point your database client at 127.0.0.1:<local>.
internalNever on your machine; only Ciresk's own services in the session reach it. Never has an origin.

Each in-pod port is used once per session (duplicate_target_port) and each port of an http or tcp port once per template (duplicate_port). Ports 3128, 3129, 3130 and 3131 belong to the session itself, and 8931 too when the template has a browser (reserved_port). On Windows, run ciresk dev setup --resolver once so <label>.localhost resolves for every program; see quickstart.md.

Egress

Nothing leaves a session by default. Each row asks for one destination:

Code
egress:
  - {host: api.stripe-mock.example.com, ports: [443], purpose: the payments sandbox the api calls, required: true}
  - {host: "*.tiles.example.org", ports: [443], purpose: map tiles in the web app}
FieldTypeDefaultWhat it does
hostDNS namerequiredA host name, or *. and a name for its subdomains. No IP address, scheme or port (egress_host_not_name). At most 253 characters, labels at most 63.
portslist of portsrequiredAt least one, none of them 0 (egress_ports).
purposestringrequiredWhy the session needs it.
requiredbooleanfalseA required row the session cannot have refuses ciresk dev up; an optional one is dropped and the session starts without it.

At ciresk dev up each row passes these checks in order; the session gets what passes all of them:

  1. Your organisation's locked egress rows, when it sets them, replace the template's rows.
  2. No model or tool vendor: rows naming a model or inference API, an AI gateway or a vendor's hosted MCP server, or a wildcard that may reach one, are never allowed.
  3. Your organisation's session ceiling, when it sets one: a row it does not cover is not allowed.
  4. For a session an agent starts, your organisation's agent ceiling. Unset, it allows nothing: an agent can edit .ciresk/dev.yaml itself, so only an admin widens what its sessions reach.
  5. The installation's allow-list, which bounds everything; on Ciresk's cloud it is Ciresk's, on your own installation it is your operator's.

A required row that fails a check refuses the session with exit 4 egress_denied. The answer lists the refused destinations as host:port in denied and names the check in reason:

reasonRefused byFix
vendorCheck 2Remove the row; a session reaches models and tools only through Ciresk's own paths.
org_policyCheck 3Ask an admin to cover the host (ciresk dev policy show, admin.md), or make the row optional.
agent_policyCheck 4Ask an admin to list the host for agents.
(none)Check 5Ask the installation's operator, or make the row optional.

The session's egress decisions are recorded every minute per host, port and the rule that allowed or refused them, and ciresk dev egress lists them with the change that would let a refused one out. See security.md for the network boundary.

Registry cache

registry_cache: true gives the session the worker's read-only cache of the public npm and crates.io registries, so pnpm install and cargo build fetch packages without an egress row. It is not an egress row and needs none. Every template that installs packages without a private registry should turn it on.

Browser

Code
browser:
  image: registry.example.com/mirror/playwright/mcp:v0.0.83@sha256:690763cbb437ee7233f538a953bb5390bf03791eb5c965b471e59d17a6132fcb
  start_url: "{origin:3000}"
  idle_stop: 10m
  identities:
    - {name: nora, login: none, cookie_max_secs: 0, revoke: none}

The browser is a Chromium in the session behind the Playwright MCP server, started the first time a client uses it. Claude Code reaches it through ciresk dev mcp browser; ciresk dev evidence captures a page from it.

FieldTypeDefaultWhat it does
imagepinned imagerequiredThe upstream Playwright MCP image, pinned by digest.
start_urlstringrequiredThe first page. Starts with {origin:<port>}.
idle_stopduration10mStops the browser after this long unused. Above 0.
contextsinteger4Concurrent browser contexts, one per client, at least 1. The limit is not enforced yet: a further client is not refused.
identitieslistnoneTest users the browser can log in as.

The browser reaches only the session's own origins; a start_url or login URL elsewhere is refused with browser_open_web_phase_2. Opening other sites from the session's browser is not available yet. A template with a browser cannot name a service browser.

Test identities

FieldTypeDefaultWhat it does
namenamerequiredWhat ciresk dev login <identity> --cookies or --revoke names.
loginsee belowrequiredHow the identity logs in.
cookie_max_secsintegerrequiredThe longest the login cookie may live, at most the template's idle in seconds. 0 for login: none.
revokenone or {http: {method, url, headers, body}}requiredHow a login is revoked. Ciresk sends the call, with the same rules as an http login, when you run ciresk dev login <identity> --revoke and before the session stops. With none, a login ends when its cookie expires, and --revoke answers identity_without_revoke; for a login: none identity there is nothing to revoke and it succeeds.
loginWhat happens
noneThe app needs no login, for example a preview mode with fixtures.
{route: /__ciresk/<path>, port: <port>}The app's own dev-only login route on an http port of the session. Ciresk mints a one-time token signed with the app's CIRESK_LOGIN_KEY, which it sets in the service that serves that port together with CIRESK_LOGIN_TTL (the identity's cookie_max_secs), and the browser opens the route with it. Paths under /__ciresk/ never reach the app through a URL from outside the session. cookie_max_secs is above 0 and the same for every identity on one port.
{http: {method, url, headers, body}}A login request Ciresk sends inside the session to a URL that starts with {origin:<port>}, with the header x-ciresk-cookie-max-secs set to the identity's cookie_max_secs. A Set-Cookie that outlives it fails the login with identity_cookie_over_bound. ciresk dev login <identity> --cookies answers the cookies; ciresk_login in ciresk dev mcp browser sets them in that client's own browser context and opens the origin. method is GET, POST, PUT, PATCH or DELETE (bad_method). A header name that is not letters, digits and -, one Ciresk sets itself (Host, Connection, Content-Length, Transfer-Encoding), or a value with a control character other than a tab, is refused with bad_header; a URL with a control character with bad_url. A login that answers an error status fails with login_failed. A literal Authorization, Cookie or Proxy-Authorization header is refused with literal_credential; a secret placeholder is refused with identity_needs_egress_secrets; secret placeholders in a login are not available yet.
{script: <path>}A Playwright script in the workspace. Not available yet: it is refused with login_script_later; use a login route or an http login.

Shell and editors

Every session gets two containers on the session base image: shell (read-write) and shell-ro (read-only). ciresk dev exec and ciresk dev ssh run in shell, ciresk dev ssh --read-only in shell-ro, and ciresk dev code opens VS Code, Cursor or Windsurf over Remote-SSH with its editor server in shell (JetBrains Gateway likewise; see editors.md). They start with the first SSH channel or exec and stop idle_stop after the last one closes. The names shell, shell-ro, ide and ide-ro are reserved (reserved_name).

FieldTypeDefaultWhat it does
shell.memorysize2GiMemory of each shell container.
shell.cpusdecimal1.0CPU share of each shell container.
shell.idle_stopduration30mHow long the shells stay up after the last channel closes. Above 0.
ide.excludelist of globsnoneJoins the editor's built-in watcher and search excludes (**/target/**, **/node_modules/**, **/.next/**, .ciresk/**).
ide.memorysizenoneWhen set, sizes shell instead of shell.memory, room for the editor server.
ide.imageimage by digestCiresk'sVS Code in the browser runs this image instead of Ciresk's: your organisation's build FROM ciresk/dev-ide with its extensions built in, so the digest fixes every extension's bytes. Pinned like a service's image; your organisation can lock it.
ide.galleryhttps urlnoneYour own Open VSX mirror, from which people may install more extensions in the read-write browser editor for the session's life. An https://host[:port][/path] URL with no query, fragment or credentials (ide_gallery_url). Refused unless a row of egress reaches its host and port (ide_gallery_not_egress), and for Microsoft's Marketplace (ide_gallery_microsoft).

Size shell for what you run with exec: a cargo build of a large workspace needs more than the 2Gi default. VS Code in the browser runs in containers of its own (ide and ide-ro) when your organisation turns it on; see editors.md.

Seed data

Code
data:
  volumes: [postgres]
  seed: [postgres-setup]
  inputs: [migrations/**]

ciresk dev reset puts the named volumes back to the state they had when the seed jobs completed, in about a second. --volume resets only some, --rebuild empties them and runs the seed jobs again.

FieldTypeDefaultWhat it does
volumeslist of volume namesrequiredVolumes declared by a service of the template; at least one.
seedlistnonePrepare steps or run: once services; the seed is taken when every one has completed.
inputslist of globsnoneFiles whose change marks the seed stale, such as migrations.

Queue

FieldTypeDefaultWhat it does
queue.slotsintegerthe session's CPUs, at least 2Operations (exec and similar) that run at once; more wait in a fair queue. At least 1.
queue.per_principalintegerhalf of slots, at least 1How many of them one person or agent may hold. At least 1, at most slots.

Idle stop and disk

A session is active while your worktree syncs to it, a stream (a URL, a port, SSH) is open, or a call names it. After idle without activity it stops: a session on your worktree ends and its volumes are deleted, while your code is safe in your worktree; a cloud session is paused with its checkpoint unless your organisation set automatic stops to end sessions (sessions.md). A session that gets no first sync within 60 seconds of starting stops too.

The template's idle is 5m to 8h (idle_out_of_range). Your organisation narrows the range with an idle minimum and maximum, and may set a default that applies instead of the template's:

CaseResult
The template names an idle over the organisation's maximumRefused at ciresk dev up with template_invalid and the finding idle_over_policy, which names the largest value allowed. An idle of exactly 60m counts as the default and is never refused.
The organisation sets a default idleIt applies instead of the template's.
OtherwiseThe template's idle (60 minutes when it names none) is moved into the organisation's range.
ciresk dev up --idle 2hYour own choice for this session; outside the range it is refused with exit 4 idle_out_of_bounds, which names both bounds.
A remote session (--remote) without --idle, when the organisation's automatic stops end sessionsThe organisation's maximum, since the session holds the only copy of its edits. Under the default, where automatic stops pause a cloud session, it gets the same idle as any session.

disk is the session's quota for its writes, volumes and caches. Your organisation may also cap the memory and CPUs one session reserves in total; a template over the cap is refused with exit 4 session_too_large.

Parameters and presets

A template can take inputs, so one template serves several ways of working: with or without demo data, with more or fewer workers. Each parameter has a type and a default, and reaches the stack only through {param:<name>} in a service's env: a parameter cannot change a size, an image or which services run.

Code
parameters:
  - {name: seed, type: choice, options: [none, demo, full], default: demo, description: Data the database starts with}
  - {name: api-workers, type: number, default: 2, min: 1, max: 8, mutable: true}
  - {name: clickhouse, type: bool, default: true, mutable: true}
presets:
  - {name: light, description: No data and no ClickHouse, values: {seed: none, clickhouse: false}}
services:
  api:
    env: {SEED: "{param:seed}", API_WORKERS: "{param:api-workers}", CLICKHOUSE: "{param:clickhouse}"}
FieldTypeDefaultWhat it does
namenamerequiredHow --param, presets and {param:<name>} refer to it.
typestring, number, bool, choicerequiredA string of at most 256 characters with no control characters, a whole number, true or false, or one of options.
defaultvaluerequiredThe value when nobody chooses one. It must pass its own type.
optionslistrequired for choicechoice only: 1 to 64 distinct values of at most 256 characters each.
min, maxwhole numbersnonenumber only. Either may be left out.
mutablebooleanfalseWhether ciresk dev up with another value starts your running session again with it. An immutable value takes --recreate.
descriptionstringemptyShown in the console's form.

The parameters are a list, and the console's form shows them in that order. Values in the file (default, options and preset values) are written as YAML strings, whole numbers or booleans; every value reaches the session as a string, such as "2" or "true".

A preset sets some parameters; the rest keep their defaults.

Preset fieldTypeDefaultWhat it does
namenamerequiredWhat --preset names.
descriptionstringemptyShown in the console's preset picker.
valuesmap of parameter name to valuenoneThe values it sets. Each names a declared parameter and passes its type.

A template has at most 32 parameters and 16 presets. No preset applies unless you choose one: there is no default preset.

Terminal
ciresk dev up kepler --preset light --param api-workers=4

The value of a parameter is its default, then the preset's, then --param's. --param <name>=<value> is repeatable and splits at the first =, so a value may hold one. An agent passes the same as parameters and preset to the dev_session_up MCP tool. A preset or parameter the template does not declare, or a value its type refuses, is refused with exit 2 parameter_invalid, and each finding says what the parameter takes.

The session records the values it runs with: ciresk dev status, the API, the console and the audit trail show them. The console's "New session" form shows the parameters and presets of the version promoted for the repository, with a preset picker that fills the fields. Your organisation can lock images, egress and container sizes over a template (Locked fields), but not parameters or presets.

Parameter values are visible to everyone who can see the session. Never put a secret in a parameter. A parameter whose name contains secret, token, password, passwd, credential, apikey, api-key or private-key is refused with parameter_secret. That rule catches the common names only; a secret belongs in your organisation's secrets (ciresk dev secrets set <name>).

With a session running, ciresk dev up compares only the parameters you name, directly or through --preset. Naming none returns the running session as it is. A different mutable value ends the session and starts a new one with it, on the same label; its volumes start fresh. A different immutable value is refused with parameter_immutable until you add --recreate. Agents using dev_session_up get parameters_changed or parameter_immutable and pass recreate: true to apply the change. ciresk dev start keeps the values a paused session was created with.

Template versions

An organisation can publish a template as a version, promote a version for one repository or for all of them, and require that every new session runs a promoted version.

Terminal
ciresk dev templates publish app
ciresk dev templates list
ciresk dev templates promote eu1_devtv_0192... --repo github.com/acme/app
ciresk dev templates promote eu1_devtv_0192... --all
CommandWhat it does
publish [<template>]Validates the template of this worktree's .ciresk/dev.yaml with the same rules up applies and records it as a version (eu1_devtv_...) with the images it pins. Without a name it publishes the template ciresk dev up would start here. Publishing the same name and file again answers the existing version. Needs dev:templates.write; a person or a service key, never an agent.
list [--template <name>]The organisation's versions, newest first: template, digest, id and the repositories each is active for. Needs dev:templates.read; agents read it with the dev_templates_list MCP tool.
promote <version_id> --repo <url> or --allMakes the version the active one of its template for one repository (a remote URL in any form git accepts, or host/owner/name) or for every repository that has no promotion of its own. It replaces the version active before, and both are recorded. Needs dev:templates.write, and only a person can promote.

A version is a template name plus the digest of the whole .ciresk/dev.yaml, so a change to any template in the file is a new version of every template in it. There is no demote: promote another version, or set the policy back to any.

Requiring a published version

The organisation policy's templates setting is any (the default: every repository's own file runs) or published. Admins change it with ciresk dev policy set; see admin.md.

Under published, a new session (ciresk dev up or ciresk dev up --recreate) starts only when its template name and dev-file digest are the version promoted for its repository; the repository's own promotion wins over --all. Otherwise it is refused with exit 4 template_not_published, whose next step is ciresk dev templates publish <template>; when another version is promoted, the answer names it in promoted (version_id, template_digest, repository). A running session keeps running, and ciresk dev up still returns it. Every edit of .ciresk/dev.yaml therefore needs a publish and a promote before the next new session.

A remote session started from the console has no worktree and no file of its own, so it always runs the version promoted for its repository, under either setting; with none promoted it is refused with template_not_published. ciresk dev up --remote sends your worktree's file like any other up.

Locked fields

An organisation can also lock a few fields over every repository's template: an image per registry repository (a template's image of that repository gets the locked tag and digest), the egress rows, and a cap on every container's memory and CPUs (larger values are lowered to the cap, not refused). The overlay applies before validation, so a locked template passes the same checks. Everything else stays the repository's. ciresk dev policy show shows what is locked; see admin.md.

Validation findings

Errors refuse ciresk dev up and publish with exit 2 template_invalid. Paths in a finding are relative to the template: services.web.ports[0].

FindingMeaningFix
yaml, unsupported_versionThe file is not valid YAML, has an unknown field or a wrong shape, or version is not 1.Fix the field the message names.
bad_name, bad_quantity, bad_duration, bad_decimalA name, size, duration or CPU value is off its format.Use the formats under "Rules for the whole file".
toolchain_refusedA toolchain name not on the list, or a version that is not plain.Name a listed tool, or install it in a prepare step.
unpinned_image, unqualified_image, unreleased_imageAn image without a digest, without its registry host, or pinned to the all-zero digest.Set <host>/<path>:<tag>@sha256:<digest> as the finding's next step says.
pull_needs_pinned_image, pull_secret_invalid, pull_user_invalidpull on image: workspace; a secret name that is not 1 to 63 lower-case letters, digits, - and _ starting with a letter or digit, or that starts with model_; a registry user that is not 1 to 255 printable characters without :.Pin an image of your registry; name the secret as ciresk dev secrets list shows it; fix the user.
resources_missing, memory_ordermemory, cpus or disk missing or 0; or memory, memory_ceiling, memory_max out of order.Declare positive values with memory at most memory_ceiling at most memory_max.
empty_command, workspace_image_needs_workspaceA prepare step or image: workspace service without a command, or an image: workspace service with workspace: none.Add the command; give the service workspace access.
idle_out_of_range, idle_over_policyidle outside 5m to 8h, or over your organisation's maximum.Lower idle; ciresk dev policy show shows the maximum.
unknown_after, bad_after, after_cycleafter names nothing, has a bad suffix, or the waits form a cycle.Name an existing step or service as <name>, <name>:ready or <name>:completed.
after_once_needs_completed, completed_needs_once, after_on_demandA wait on a step without :completed, :completed on a service, or an eager service waiting on an on-demand one.Match the suffix to the target; make the waiting service on demand too.
once_on_demand, idle_stop_needs_on_demandA run: once service on demand, or idle_stop on an eager service.Remove start: on_demand or idle_stop.
duplicate_container_name, reserved_nameTwo steps or services share a name (a service named browser in a template with a browser counts), or a name, volume or file is one Ciresk uses: the containers shell, shell-ro, ide and ide-ro; the volumes shell-home, ide-home, ide-run, ide-ro-run and cargo-home; the files ide-settings, ide-web-settings and cargo-config.Rename it.
reserved_port, duplicate_target_port, duplicate_port, duplicate_port_nameA port the session uses itself, or a port or port name declared twice.Move or rename the port.
unknown_placeholder, origin_port_unknown, origin_not_http, origin_internal_port, origin_target_mismatchA placeholder that does not exist, or {origin:<port>} without an http port whose port and target are that number.Declare the port as http with the same port and target.
reserved_envenv sets a variable Ciresk sets.Remove it.
bad_probe, bad_diagnosticsA readiness probe or diagnostics entry with none or more than one of its keys.Give exactly one.
serves_needs_workspace_image, hmr_needs_probe, diagnostics_needs_tsc_jsonserves other than static on a pinned image, hmr without hmr_probe, or diagnostics without a tsc_json source.Match serves to what the service has.
probe_needs_serves_hmr, probe_port_not_own, probe_needs_nonce, probe_timingAn hmr_probe on a service that is not serves: hmr, on a port that is not its own http port, without exactly one {nonce}, or with every under slo.Fix the probe field the path names.
diagnostics_needs_workspace_image, diagnostics_outside_mount, diagnostics_port_not_ownA file source on a pinned image or outside /run/ciresk/diag/, or next_mcp on a port that is not this service's http port.Move the file or the port.
heal_trigger_unmeasured, growth_factor_lowA heal trigger the service has no probe for, or growth_factor under 1.5.Add the probe or remove the trigger; raise the factor.
bad_path, file_in_workspace, cache_max_without_dirsA path that must be absolute (or workspace-relative) is not, contains .., a generated file lands in the workspace, or cache_max has no cache_dirs.Fix the path; add cache_dirs.
bad_glob, shared_without_inputs, shared_after, layer_inputs_not_sharedA glob with .., braces or classes; a shared step without inputs or with after; layer_inputs on a step that is not shared.Fix the glob; list the step's inputs and drop its after; set shared: true or drop layer_inputs.
egress_host_not_name, egress_portsAn egress host that is not a DNS name, or a row without ports.Write the host name and at least one port.
browser_open_web_phase_2, bad_contextsA browser URL that does not start at a session origin, or contexts: 0.Start the URL with {origin:<port>}; use at least 1.
ide_gallery_url, ide_gallery_microsoft, ide_gallery_not_egresside.gallery is not a plain https:// URL, names Microsoft's Marketplace, or has no egress row for its host and port.Point it at your Open VSX mirror and add its egress row.
bad_login, bad_revoke, identity_cookie_over_idle, identity_none_has_no_cookie, literal_credential, identity_needs_egress_secrets, bad_method, bad_header, bad_url, login_script_laterAn identity's login or revoke has the wrong shape, its cookie outlives idle, login: none with a cookie, a credential in a login header, a method or header Ciresk does not send, or a script login, which is not run yet.Fix the identity as its row under "Test identities" says.
login_route_not_filtered, login_port_unknown, login_route_needs_cookie_bound, login_ttl_conflictA login route outside /__ciresk/, on a port no service serves over http, with cookie_max_secs: 0, or two identities on one port with different cookie lifetimes.Fix the route, the port or cookie_max_secs.
data_volumes_empty, data_unknown_volume, data_seed_not_oncedata without volumes, a volume no service declares, or a seed that is not a step or run: once service.Name declared volumes and run-once seeds.
bad_queueA queue value of 0, or per_principal over slots.Use at least 1 and at most slots.
sealed_needs_image, sealed_volume_shared, share_pending_ownerA sealed service on image: workspace or sharing a volume; share: per_worker.Pin an image and give it its own volumes; use per_session.
template_blockedThe template you start has a description that starts with blocked:.Do what its next step says, then remove the prefix.
parameter_duplicate, preset_duplicate, parameters_too_manyTwo parameters or presets share a name, or more than 32 parameters or 16 presets.Rename or remove one.
parameter_default_invalid, parameter_options_invalid, parameter_bounds_invalid, parameter_field_invalidA default its type refuses; a choice without options, or an empty, repeated or over-long options list; min over max; or options, min or max on a type without them.Fix the parameter the path names.
parameter_secretA parameter's name reads like a secret.Use an organisation secret: ciresk dev secrets set <name>.
preset_unknown_parameter, preset_value_invalidA preset sets a parameter the template does not declare, or a value its type refuses.Name a declared parameter with a value it takes.
unknown_parameter, parameter_env_only{param:<name>} names no parameter, or appears in the browser's start_url or an identity's login URL.Declare the parameter; use it in a service's env.
on_demand_owns_no_port, workspace_serves_staticWarnings: an on-demand service no request can start, or workspace code reported as always current.Give it a tcp or http probe; set serves to what the service proves.

Other refusals at ciresk dev up

CodeExitMeaningFix
no_template2No .ciresk/dev.yaml, no template of that name, or no way to pick one.Name the template: ciresk dev up <name>.
egress_denied4A required egress row is not allowed.Follow reason.
template_not_published4The policy requires a promoted version and this file is not it.ciresk dev templates publish <template>, then promote it.
idle_out_of_bounds4--idle outside the organisation's range.Pick a value within the bounds in the answer.
session_too_large4The template reserves more memory or CPUs than the organisation allows one session.Lower memory or cpus; ciresk dev policy show shows the cap.
parameter_invalid2--param or --preset names nothing the template declares, or a value its type refuses. Each finding says what the parameter takes.Fix the value.
parameter_immutable4The running session has another value of an immutable parameter you named.ciresk dev up --recreate with the same flags.
parameters_changed4Only for an agent's dev_session_up: the running session has other values of the mutable parameters it named. ciresk dev up answers it by starting the session again.Call again with recreate: true.
pull_secret_not_granted2A pull.secret that your organisation's secrets do not grant to this session.ciresk dev secrets set <name> --host <registry> --repo <repository> (secrets.md).