Authoring Workshop Templates
A workshop template is a single YAML file that describes one entry in
Orchestra’s catalog — the RStudio, JupyterLab, or other environment a
participant can launch. It’s a friendly projection of the Workshop CRD spec: a
handful of fields (image, port, resources, duration…) that the platform turns
into a fully-formed Workshop custom resource on each launch.
Templates are git-managed (ADR-0006). They live in the Helm chart at:
deploy/charts/orchestra/files/templates/├── jupyter.yaml├── rstudio.yaml├── template.schema.json # generated — do not hand-edit└── README.mdThese files are the source of truth, not the database. The chart renders them into a ConfigMap mounted into the API pod, which loads them into an in-memory registry at startup — there is no admin UI and no runtime database toggle. A catalog change is a change to these files, whether the submission form writes it for you (the front door) or you hand-author the file and open the PR yourself (the fallback).
The file-per-template model
Section titled “The file-per-template model”- One file per template, named
<slug>.yaml. The filename stem must equal the template’sslug(validation enforces this). - Slugs must be unique across the directory.
- To retire a template without deleting its file, set
enabled: false.
Field reference
Section titled “Field reference”Every field below is defined by the WorkshopTemplateFile model in the
orchestra-template-tools package — the single source of truth from which
template.schema.json is generated. Fields accept camelCase (shown here) or
snake_case; the schema uses camelCase.
Top-level fields
Section titled “Top-level fields”| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
name | string | yes | — | Human-readable display name shown in the catalog. |
slug | string | yes | — | k8s-safe id: lowercase alphanumerics and -, must start/end alphanumeric, max 40 chars. Prefixes instance names; must match the filename stem. |
description | string | null | no | null | Optional catalog description. |
image | string | no | rocker/rstudio:latest | Default container image for the session. |
defaultDuration | string | no | 4h | Default session lifetime (e.g. 2h, 4h). |
port | integer (1–65535) | no | 8787 | Port the app listens on inside the container (RStudio 8787, JupyterLab 8888). The sidecar proxies to http://localhost:<port>. |
env | map<string,string> | no | {} | Extra environment variables for the app container. Override operator defaults such as DISABLE_AUTH. |
args | list<string> | no | [] | Container args, replacing the image’s default CMD (e.g. JupyterLab launch flags). Leave empty to use the image default. |
tier | small | large | no | small | Tenant node-pool tier. Maps to nodeSelector/tolerations in the operator when tenant pools are enabled. |
resources | object (see below) | no | model defaults | CPU/memory/ephemeral-storage limits and requests. |
storage | object | null | no | null | Per-session persistent volume. Unset means no dedicated PVC beyond the defaults. |
tags | list<string> | no | [] | Category tags for catalog filtering. |
enabled | boolean | no | true | Whether the template is shown and launchable. false retires it without deleting the file. |
url | string | null | no | null | Landing/materials page for the catalog’s “Learn more” link (the form’s Landing URL). Catalog metadata; never reaches the CRD. |
sourceUrl | string | null | no | null | Repository the workshop content lives in, for the catalog’s “Source” link (the form’s Source repo URL). Catalog metadata; never reaches the CRD. |
submittedBy | string | null | no | null | GitHub handle of the submitter, stamped by the front door from the issue author (ADR-0009); absent on hand-authored templates — you don’t set this. |
resources
Section titled “resources”All resource fields are strings using Kubernetes quantity notation (e.g. "2",
500m, 4Gi).
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
cpu | string | no | 1 | CPU limit. |
memory | string | no | 2Gi | Memory limit. |
cpuRequest | string | no | 500m | CPU request. |
memoryRequest | string | no | 1Gi | Memory request. |
ephemeralStorage | string | no | 8Gi | Ephemeral-storage limit — everything written outside the /data PVC (package installs, /tmp, the container writable layer). The kubelet evicts the pod if exceeded. |
ephemeralStorageRequest | string | no | 8Gi | Ephemeral-storage request. |
storage
Section titled “storage”| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
size | string | no | 10Gi | Persistent volume size for the session’s /data. |
storageClass | string | null | no | null | Storage class name. Leave unset (or empty) to use the cluster default. |
Starter template
Section titled “Starter template”Copy this to <slug>.yaml, rename the slug (and file) to match, and edit. It
validates as-is:
# yaml-language-server: $schema=./template.schema.jsonname: My Workshopslug: my-workshopdescription: >- A one-line description of what participants get.image: bioconductor/bioconductor_docker:RELEASE_3_20defaultDuration: 4hport: 8787tier: smallresources: cpu: '2' memory: 4Gi cpuRequest: '1' memoryRequest: 2Gi ephemeralStorage: 8Gi ephemeralStorageRequest: 8Gistorage: size: 10Gitags: - bioconductorenabled: trueFor JupyterLab and other stock images, see
Configuring a Workshop Image for the
exact port, env, and args needed to start the app unauthenticated behind
the proxy.
Validating your template
Section titled “Validating your template”In your editor
Section titled “In your editor”The first line of every template is a schema directive:
# yaml-language-server: $schema=./template.schema.jsonThis is a repo-relative path to the generated template.schema.json that
sits next to the templates — so validation works offline, in any checkout, with
no URL to rot. With the YAML extension
(VS Code, or any editor speaking the YAML language server), you get
autocomplete, type checking, and required-field warnings as you type.
The schema is generated, never hand-edited. Regenerate it after changing the model:
just template-schemaFrom the command line
Section titled “From the command line”Validate every file in the directory with the same CLI that CI runs:
just validate-templatesThis checks each file against the schema, and catalog-level rules: unique slugs and filename-matches-slug. It exits non-zero on any error.
Hand-authoring workflow (the fallback)
Section titled “Hand-authoring workflow (the fallback)”-
Edit a YAML file. Copy an existing template (e.g.
rstudio.yaml) to<slug>.yamland change the fields. Your editor validates against the schema as you go. -
Validate locally with
just validate-templates(andjust check-schemaif you touched the model). -
Open a PR. CI runs the same validation on every file.
-
An admin reviews and merges. Templates are the platform’s catalog, so changes go through review.
-
Deploy rolls it out. The next
helm upgradere-renders the ConfigMap and restarts the API pod, which reloads the registry from the files. The new or changed template appears in the catalog.