Workshop definition¶
A workshop definition is the YAML file that Workshop reads to launch and refresh a workshop. It names the base image, lists the SDKs to install, declares any extra plugs, slots, or connections, and records reusable shell actions. The file is authored by the workshop’s user.
Filename and location¶
A project may store a single workshop definition at its root,
or several under .workshop/.
A single workshop:
workshop.yamlor.workshop.yamlin the project directory.Multiple workshops:
.workshop/<NAME>.yaml, one file per workshop. The<NAME>part of the filename must equal the workshop’snamefield.A workshop name must start with a lowercase letter and may contain lowercase letters, digits, and hyphens between them. Up to 40 characters.
Top-level fields¶
Key |
Value |
Description |
|---|---|---|
|
string |
Workshop identifier. Subject to the naming rules above.
Must match the filename when the definition is under |
|
string |
Base operating system image.
One of SDKs that declare a |
|
array |
Ordered list of SDK entries. Each entry references an existing SDK and configures it for the workshop. The system SDK is installed first implicitly and is not required here. See SDK entry. |
|
array |
Explicit connections between plugs and slots, applied on top of Workshop’s auto-connect logic. See Connection entry. |
|
object |
Named shell scripts available via workshop run. See Action entry. |
Nested structures¶
SDK entry¶
Each item in sdks is an object with these fields:
Key |
Value |
Description |
|---|---|---|
|
string |
SDK identifier. The underlying name must contain
at least one lowercase letter and may consist of
lowercase letters, digits, and hyphens between them.
Use a prefix to select the source:
The fully prefixed name is at most 40 characters without a prefix,
44 with |
|
string |
Store channel from which to retrieve the SDK
at launch
and refresh.
Uses the snap channel format:
Default: Note Quote channel values in YAML when they look numeric
(for example, |
|
object |
Plug bindings or additional plug definitions grafted onto the SDK by this workshop. See Plug or slot entry (under an SDK) and Interfaces. |
|
object |
Additional slot definitions grafted onto the SDK by this workshop.
Each entry specifies the |
Plug or slot entry (under an SDK)¶
Each plug under an SDK is either an inline plug definition or a binding to another plug. Slots under an SDK are always inline slot definitions; slots cannot be bound.
Key |
Value |
Description |
|---|---|---|
|
string |
Required for an inline plug definition; identifies the interface
(for example, |
|
string |
Reference to a target plug, in the form A bound plug must not carry any other attributes,
cannot belong to the system SDK, cannot chain
(bind to a plug that is itself bound),
and cannot also appear in |
any interface attribute |
varies |
Inline plug definitions accept the attributes documented under Interfaces. |
Connection entry¶
Each item in connections links a plug to a slot of the same interface:
Key |
Value |
Description |
|---|---|---|
|
string |
Plug reference, in the form |
|
string |
Slot reference, in the form |
A plug that has a bind set under its SDK entry
cannot also be listed in connections.
Action entry¶
Each entry in actions maps an action name to a shell script body:
Key |
Value |
Description |
|---|---|---|
action name |
string |
Must start with a lowercase letter and may contain lowercase letters, digits, and hyphens between them. |
action body |
string |
A bash script.
Workshop sets |
Actions are interpreted lazily:
edits to actions are available immediately,
without workshop refresh.
Interfaces¶
The attributes accepted by inline plug and slot definitions depend on the interface. These same attributes appear in SDK definitions (SDK definition and SDKcraft project definition); a workshop may graft additional plugs and slots that follow them.
Camera interface¶
The camera interface exposes a host camera device.
Plug attributes: none.
Plug name: must be
camera.Plug owner: any regular SDK; not the system SDK.
Slot: the system SDK provides a single
system:cameraslot. Other SDKs cannot declare camera slots.
Custom device interface¶
The custom device interface exposes host devices that belong to a Linux kernel subsystem.
A custom device plug is described by this attribute:
Key |
Value |
Description |
|---|---|---|
|
string |
The Linux kernel subsystem of the host devices to expose,
for example |
Plug owner: any regular SDK; not the system SDK.
Slot: the system SDK provides a single system:custom-device slot.
Other SDKs cannot declare custom device slots.
Desktop interface¶
The desktop interface exposes the host display server.
Plug attributes: none.
Plug name: must be
desktop.Plug owner: any regular SDK; not the system SDK.
Slot: the system SDK provides a single
system:desktopslot. Other SDKs cannot declare desktop slots.
GPU interface¶
The GPU interface exposes host GPU devices.
Plug attributes: none.
Plug name: must be
gpu.Plug owner: any regular SDK; not the system SDK.
Slot: the system SDK provides a single
system:gpuslot. Other SDKs cannot declare GPU slots.
Mount interface¶
The mount interface exposes a directory between a slot owner and a plug owner.
A mount plug is described by these attributes:
Key |
Value |
Description |
|---|---|---|
|
string |
Path inside the workshop used as the plug’s target directory.
Must be an absolute path;
|
|
integer |
File permissions, in octal, applied when creating |
|
integer |
User ID applied when creating |
|
integer |
Group ID applied when creating |
|
Boolean |
Whether the target directory should be read-only.
Defaults to |
Plug owner: any regular SDK; not the system SDK.
The system SDK provides one mount slot, system:mount,
with a dynamic host-source attribute
that can be configured only at remount.
It is the only mount slot whose source is on the host filesystem.
A mount slot on a regular SDK is described by this attribute:
Key |
Value |
Description |
|---|---|---|
|
string |
Path inside the workshop used as the slot’s source directory.
Must be an absolute path;
|
SSH interface¶
The SSH interface exposes the user’s SSH agent socket.
Plug attributes: none.
Plug name: must be
ssh-agent.Plug owner: any regular SDK; not the system SDK.
Slot: the system SDK provides a single
system:ssh-agentslot. Other SDKs cannot declare SSH slots.
Tunnel interface¶
The tunnel interface forwards a network address or Unix domain socket.
Both tunnel plugs and tunnel slots take a single attribute:
Key |
Value |
Description |
|---|---|---|
|
string |
Network address or Unix domain socket that forms one end of the tunnel.
Defaults to |
The endpoint value follows this grammar:
Field |
Format |
|---|---|
Endpoint |
|
Address |
|
Protocol |
Either |
Host |
An IPv4 or IPv6 address. When a port is supplied, IPv6 addresses must be enclosed in square brackets. Supported aliases: |
Port |
A TCP or UDP port number (1-65535). May be omitted, but only on one side of a connection; both sides then use the same port. For security, tunnel plugs in the system SDK cannot use privileged ports (1-1023). |
Path |
Absolute path to a Unix domain socket.
For security, tunnel plugs in the system SDK cannot listen on sockets outside these two directories. |
String |
An abstract socket name. |
Endpoints that start with [ or @ must be quoted in YAML:
endpoint: '[::1]:8080/tcp'
endpoint: '@abstract.sock'
JSON Schema¶
The following JSON Schema describes the structure above:
Workshop definition schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://canonical.com/workshop.yaml",
"title": "Workshop",
"description": "Workshop definition.",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Workshop name. Must start with a lowercase letter and may contain lowercase letters, digits, and hyphens between them. Up to 40 characters. Must match the definition file basename if the workshop is stored under .workshop/.",
"pattern": "^[a-z](?:-?[a-z0-9])*$",
"maxLength": 40,
"errorMessage": "A workshop's name must start with a lowercase letter and can only include digits, lowercase letters, and hyphens joining them."
},
"base": {
"type": "string",
"description": "Base operating system image for the workshop. Must be one of the supported Ubuntu releases. SDKs with a declared base must match the workshop base; SDKs without a base are accepted on any workshop.",
"enum": [
"ubuntu@20.04",
"ubuntu@22.04",
"ubuntu@24.04",
"ubuntu@26.04"
],
"errorMessage": "The base must be one of the supported values: ubuntu@20.04, ubuntu@22.04, ubuntu@24.04, ubuntu@26.04."
},
"sdks": {
"type": "array",
"description": "Ordered list of SDKs to install on top of the base. Each entry references an existing SDK; names must be unique within the list. The system SDK is installed first implicitly and need not be listed.",
"uniqueItems": true,
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "SDK name. Prefix with try- for a locally tried SDK, project- for an in-project SDK, or use system for the built-in system SDK; an unprefixed name resolves to an SDK from the Store. The underlying name must contain at least one lowercase letter, may consist of lowercase letters, digits, and hyphens between them, and cannot be agent. Without a prefix the name is at most 40 characters; with try- at most 44; with project- at most 48.",
"if": {
"pattern": "^try-"
},
"then": {
"maxLength": 44,
"pattern": "^(?!(?:try-|project-)?agent$)try-(?!try-|project-)(?:[a-z0-9]-?)*[a-z](?:-?[a-z0-9])*$"
},
"else": {
"if": {
"pattern": "^project-"
},
"then": {
"maxLength": 48,
"pattern": "^(?!(?:try-|project-)?agent$)project-(?!try-|project-)(?:[a-z0-9]-?)*[a-z](?:-?[a-z0-9])*$"
},
"else": {
"maxLength": 40,
"pattern": "^(?!(?:try-|project-)?agent$)(?!try-|project-)(?:[a-z0-9]-?)*[a-z](?:-?[a-z0-9])*$"
}
},
"errorMessage": "An SDK's name must contain a letter, may have a single 'try-' or 'project-' prefix, must not chain prefixes, and the underlying name cannot be 'agent'."
},
"channel": {
"type": "string",
"description": "Store channel used to retrieve the SDK at launch and refresh. Snap-like format: [<TRACK>/]<RISK>[/<BRANCH>], or <TRACK>, or <RISK>/<BRANCH>. Risk is one of stable, candidate, beta, edge. Track is at most 28 characters; branch is at most 128 characters; neither may equal a risk name. Default is latest/stable. Only applies to SDKs from the Store; for try-, project-, and system SDKs the value has no effect but must still be well formed.",
"pattern": "^(?:(?:[a-zA-Z0-9](?:[_.-]?[a-zA-Z0-9])*/)?(?:stable|candidate|beta|edge)(?:/[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9])?|[a-zA-Z0-9](?:[_.-]?[a-zA-Z0-9])*)$",
"errorMessage": "Channel must look like [<track>/]<risk>[/<branch>] or [<track>]."
},
"plugs": {
"type": "object",
"description": "Plug bindings and additional plug definitions for the SDK. Each key must start with a lowercase letter and contain only lowercase letters, digits, and hyphens between them.",
"additionalProperties": false,
"patternProperties": {
"^[a-z](?:-?[a-z0-9])*$": {
"description": "Either an inline plug definition (a mapping whose keys are interface-specific attributes such as interface, workshop-target, endpoint), a string naming the interface, null to use the key as the interface name, or a binding to another plug declared in the same workshop. To bind, provide a single bind key whose value is the target plug reference; bound plugs cannot define other attributes and cannot also appear in connections.",
"properties": {
"bind": {
"type": "string",
"description": "Plug reference in the form <sdk>:<plug>. The target plug must use the same interface as this plug, must not itself be bound, and must not belong to the system SDK (so the <sdk> portion must name a non-system SDK).",
"pattern": "^(?!system:)(([a-z0-9]-?)*[a-z](-?[a-z0-9])*):[a-z](-?[a-z0-9])*$",
"errorMessage": "Bind reference must follow the pattern <sdk>:<plug>, where <sdk> names a non-system SDK."
}
},
"if": {
"type": "object",
"required": [
"bind"
]
},
"then": {
"type": "object",
"properties": {
"bind": true
},
"additionalProperties": false,
"errorMessage": "When 'bind' is set, no other attributes are allowed on the plug."
}
}
}
},
"slots": {
"type": "object",
"description": "Additional slot definitions grafted onto the SDK by the workshop. Each key must start with a lowercase letter and contain only lowercase letters, digits, and hyphens between them. Each value is either an inline slot definition (a mapping with interface and any interface-specific attributes), a string naming the interface, or null to use the key as the interface name.",
"patternProperties": {
"^[a-z](?:-?[a-z0-9])*$": {
"type": ["object", "string", "null"],
"description": "Slot definition: an inline mapping with interface and any interface-specific attributes, a string naming the interface, or null to take the interface from the key."
}
},
"additionalProperties": false
}
},
"required": [
"name"
],
"errorMessage": {
"required": {
"name": "Each SDK must specify a name."
}
},
"additionalProperties": false
}
},
"connections": {
"type": "array",
"description": "Explicit connections from plugs to slots, applied on top of auto-connection. Both endpoints must reference an SDK that is present in the workshop or is implicit (system, sketch), and must share the same interface. A plug that is bound elsewhere cannot also appear here.",
"items": {
"type": "object",
"properties": {
"plug": {
"type": "string",
"description": "Plug reference in the form <sdk>:<plug>. The <sdk> portion may be empty (for example, :ssh-agent) to refer to the system SDK.",
"pattern": "^(([a-z0-9]-?)*[a-z](-?[a-z0-9])*)?:[a-z](-?[a-z0-9])*$",
"errorMessage": "Plug reference must follow the pattern <sdk>:<plug> (the <sdk> portion may be empty for the system SDK)."
},
"slot": {
"type": "string",
"description": "Slot reference in the form <sdk>:<slot>. The <sdk> portion may be empty (for example, :ssh-agent) to refer to the system SDK.",
"pattern": "^(([a-z0-9]-?)*[a-z](-?[a-z0-9])*)?:[a-z](-?[a-z0-9])*$",
"errorMessage": "Slot reference must follow the pattern <sdk>:<slot> (the <sdk> portion may be empty for the system SDK)."
}
},
"required": [
"plug",
"slot"
],
"errorMessage": {
"required": {
"plug": "Each connection must specify a plug.",
"slot": "Each connection must specify a slot."
}
},
"additionalProperties": false
}
},
"actions": {
"type": "object",
"description": "Named shell scripts available via workshop run. Action names must start with a lowercase letter and contain only lowercase letters, digits, and hyphens between them. Each script body runs under bash as a login shell with errexit and pipefail set; positional parameters $@, $1, $2, ... receive the arguments passed after the workshop name.",
"patternProperties": {
"^[a-z](?:-?[a-z0-9])*$": {
"type": "string",
"description": "Shell script body executed by bash inside the workshop."
}
},
"additionalProperties": false,
"errorMessage": "Action names must start with a lowercase letter and contain only lowercase letters, digits, and hyphens between them."
}
},
"required": [
"name",
"base"
],
"additionalProperties": false,
"errorMessage": {
"required": {
"name": "The 'name' field is required.",
"base": "The 'base' field is required."
}
}
}
Examples¶
Minimal workshop with one Store SDK and two actions:
name: golang
base: ubuntu@22.04
sdks:
- name: go
channel: "1.26"
actions:
lint: |
go vet
golangci-lint run
tests: go test "$@"
Workshop with an in-project SDK and a plug binding between SDKs:
name: go-dev
base: ubuntu@22.04
sdks:
- name: go
channel: edge
- name: project-cache
plugs:
data:
bind: go:mod-cache
Workshop that grafts a plug and a slot onto its SDKs
and adds explicit connections;
besides using the fictional
tensorflow, imagenet and cuda SDKs,
it defines an additional slot under the imagenet SDK,
a plug under tensorflow,
and two connections:
One that connects the
tensorflow:imagesplug to the newly definedimagenet:imagesslot.Another that connects the
tensorflow:cudaplug to the preexistingcuda:libs.
name: digits-cuda
base: ubuntu@22.04
sdks:
- name: tensorflow
plugs:
cuda:
interface: mount
workshop-target: /usr/local/cuda/lib64
- name: imagenet
slots:
images:
interface: mount
workshop-source: $SDK/images
- name: cuda
connections:
- plug: tensorflow:cuda
slot: cuda:libs
- plug: tensorflow:images
slot: imagenet:images
Workshop that pulls an SDK from the try area:
name: try-go
base: ubuntu@24.04
sdks:
- name: try-go
See also¶
Explanation:
Reference: