Workshop concepts¶
A workshop (lowercase; not to be confused with Workshop itself) is a container that enables consistent environment builds. A workshop is defined by a single YAML file that acts as the blueprint for Workshop to implement at launch time. It describes how individual components fit together to create a cohesive development environment. A project is the working directory where workshop definitions are placed. When you start a workshop, the project directory is mounted inside it, so storing repositories, code, or data such as models in the project directory enables you to use them inside the workshop.
Currently, these containers are hosted by LXD, but it’s not recommended to rely on this implementation detail.
Workshop status¶
A workshop’s lifecycle can see it switch between several statuses:
State |
Description |
|---|---|
Off |
Just defined, not operational; the workshop container does not exist yet. |
Ready |
Operational; the workshop container is running and ready for use. |
Stopped |
Operational; the workshop container is stopped and can be restarted. |
Pending |
Not operational; the workshop container is running but is being updated and is not ready for use. |
Waiting |
Operational; the workshop container is running and available for command execution, typically for debugging a launch or refresh error; the current change is in progress. |
Error |
Not operational; the workshop is in a nonfunctional state due to an error. |
Status diagrams in the See also section below provide more details of valid transitions.
Launch, refresh, and restore¶
Three commands move a workshop between the statuses listed above:
workshop launch builds a workshop for the first time
workshop refresh updates an existing workshop to match its current definition
workshop restore rolls a workshop back to the state it had right after its last successful launch or refresh.
The table below summarizes when to use each command and what it does to the workshop and to its interface connections:
Command |
Use case |
Workshop effect |
Connections effect |
|---|---|---|---|
workshop launch |
The workshop has never been built and is in the Off state. |
Builds the workshop from scratch, installing the base image and each SDK in order. |
All auto-connect candidates are evaluated and connected for the first time. |
workshop refresh |
The definition has changed and you want those changes applied to the running workshop. |
Reuses snapshots for SDKs whose configuration is unchanged; reinstalls the rest from scratch. |
Re-evaluates auto-connect against the new definition; any connections established manually after launch are dropped. |
workshop restore |
You want to discard runtime drift in the workshop and return it to a known-good state. |
Rolls the workshop filesystem back to the snapshot taken at the last successful launch or refresh. |
Re-evaluates auto-connect against the unchanged definition; any connections established manually since the snapshot are dropped. |
Launch¶
First, workshop launch is the one-time builder. It applies the workshop definition layer by layer, taking a ZFS snapshot after each SDK so that later operations can reuse the work; see SDKs for the layering details.
Once a workshop has been launched, running workshop launch against it again fails with no effect. To apply changes from the definition to a launched workshop, use workshop refresh.
Refresh¶
Next, workshop refresh updates an existing workshop to match its current definition file. The workshop must be in the Ready state.
Refresh is incremental:
SDKs whose configuration is unchanged
are kept as-is and restored from their snapshots
without re-running their setup-base hook,
while SDKs that have been added, removed, or changed in the definition
go through a fresh installation.
The save-state hook of each surviving SDK
runs before the rebuild,
and the matching restore-state hook runs after it,
so SDK state kept by these hooks carries across the refresh.
Restore¶
Finally, workshop restore runs the same machinery as refresh, but uses the workshop’s state from the last successful launch or refresh as both the source and the target, ignoring any edits made to the workshop definition since then. The workshop must be in the Ready state. No SDK changes are applied; the container filesystem is rolled back to the snapshot it had right after the last successful launch or refresh, discarding any changes made inside the workshop since then.
Restore is the right tool when the workshop has inadvertently drifted at runtime (for example, packages installed ad-hoc inside the container) and you want a clean slate without rebuilding from scratch.
Workshop definition¶
This is a YAML file that lists the base image of the workshop and the specific components installed on top of it. The definition acts as a single source of truth about the workshop. It usually takes a few tries to produce a definition that works for your project, so you can edit and update the file iteratively.
A simple definition might look like this:
name: dev
base: ubuntu@22.04
sdks:
- name: go
channel: 1.26
It specifies a base and an SDK. A more complete definition would usually list several SDKs that use different interfaces, software packages, and hooks.
Base image¶
The base specifies the underlying operating system image, such as a particular Ubuntu LTS release. This is the first layer of the workshop, upon which all other components are applied.
For details on how the images are handled behind the scenes, see Images.
SDKs¶
The sdks section brings in the features and tools,
layering them on top of the base image.
Each SDK listed here is a bundle of code, data, and configurations,
prepackaged with SDKcraft to be used with Workshop;
see SDK concepts for details.
This layering is not just conceptual; at launch time, Workshop uses ZFS snapshots to separate the SDKs:
The
baseOS is installed.The
systemSDK is installed, and itssetup-basehook is run.A ZFS snapshot is taken, and cloned to create a new ZFS file system.
For each subsequent SDK in the order of their appearance on the
sdkslist, itssetup-basehook is run and another snapshot is taken and cloned.
This will create a chain of snapshots,
where each one represents a cumulative layer of the workshop.
Snapshots makes operations like refreshing or reverting a workshop very fast,
as Workshop can simply restore a previous snapshot
instead of rebuilding the environment from scratch.
No snapshots are created for other hook types,
such as setup-project or save-state.
In order to restore an old snapshot, newer snapshots must be destroyed first. If refreshing fails, the workshop reverts to its previous state. The cloned file systems are used to restore the deleted snapshots.
For details on how Workshop leverages ZFS, see Storage backends.
Plugs, slots, connections¶
Once all the SDKs are installed, they often need to communicate with each other or with the host system. This is handled by establishing interface connections between plugs (service consumers) and slots (service providers); see Interface concepts for details.
These plugs and slots can be defined in two ways:
By the SDK itself: An SDK can define its own plugs and slots in its
sdk.yamlfile. These are the standard capabilities the SDK offers. For Store SDKs, thesdk.yamlfile is generated by SDKcraft; plugs and slots are copied as-is fromsdkcraft.yaml.Grafted by the workshop: A workshop definition can add plugs or slots to an SDK it references. This is done within an SDK’s entry in the
workshop.yamlfile. Grafting extends an SDK’s capabilities locally, possibly without the SDK publisher’s involvement or expectation; the user can add interface elements that the publisher didn’t anticipate, reducing the need for manual post-launch configuration.
The connections section of the definition can explicitly link
any plugs and slots available within the workshop,
on top of what the auto-connection mechanism
in Workshop provides:
eventually, all interface connections are
resolved, validated, and established
in a single task after all the SDK layers have been created,
because all components must be in place before the wiring can be done.
This example adds a slot, a plug, and a connection to its SDKs:
base: ubuntu@22.04
name: dev
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
This extends the tensorflow SDK
with a standard path for CUDA runtime libraries.
In connections,
we explicitly connect the cuda plug,
newly defined under the tensorflow SDK,
to the libs slot from the cuda SDK.
Thus, upon workshop creation,
the plug will be connected
not to a default system SDK location on the host
(for example, .../<ID>/<WORKSHOP>/...),
but to a library path inside the workshop,
which is set by workshop-target.
Mind that the connection established in this way is no different from those created via the command line.
Connections across refresh and restore¶
Interface connections fall into three observable categories, each treated differently when a workshop is refreshed or restored:
Auto-connections are established at launch from the SDK’s auto-connect rules and from the
connectionssection of the workshop definition.Manual runtime connections are added with workshop connect after the workshop has been launched and are not present in the workshop definition.
Manual disconnects of an auto-connection are made with workshop disconnect against a connection that the workshop established by itself.
The table below summarizes how each category is treated by workshop refresh and workshop restore:
Connection type and state |
After workshop refresh |
After workshop restore |
|---|---|---|
Auto-connection still valid in the new definition |
Re-established |
Re-established |
Auto-connection whose plug or slot is removed in the new definition |
Dropped |
Not applicable; definition doesn’t change |
Manual runtime connection added with workshop connect |
Dropped |
Dropped |
Manually disconnected auto-connection |
Stays disconnected |
Stays disconnected |
The practical consequence is that
runtime use of workshop connect
should be reserved for short-lived experimentation:
to make a connection that survives a refresh,
add it to the connections section of the workshop definition.
Conversely, a deliberate workshop disconnect
is preserved across refreshes and restores,
so once a default auto-connection has been turned off,
it stays off until explicitly reconnected.
Actions¶
Another optional part of a workshop definition is the actions section;
it contains named shell scripts to be copied and executed inside the workshop.
This section provides a degree of convenience,
allowing the users to define simple aliases
for longer or more complex shell commands
that they expect to run frequently inside the workshop,
right in the definition file.
Because action bodies are bash scripts,
they receive the trailing arguments of workshop run
as standard positional parameters.
Use "$@" to forward every argument
and "$1", "$2", and so on to pick individual ones.
See How to add actions to your workshop for an example.
Actions are not part of the layered snapshot system at all. They stay in the definition, and are parsed by the daemon every time the workshop run command is executed. This means the users can add or modify actions and use them immediately, without needing to refresh or restart the workshop.
The following example adds four actions,
lint, shellcheck, unit, and cover,
intended as utility helpers for a development environment:
name: dev
base: ubuntu@24.04
sdks:
- name: go
channel: 1.26
actions:
lint: |
golangci-lint run --out-format=colored-line-number -c .golangci.yaml
shellcheck: |
git ls-files | file --mime-type -Nnf- | grep shellscript | cut -f1 -d: | xargs shellcheck --check-sourced --external-sources
unit: |
go test "$@" ./...
cover: |
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out
To run these actions, you use the workshop run command:
$ workshop run dev -- lint
When you thus invoke an action, it’s injected into the workshop
and executed there in a fashion similar to workshop exec.
Even if you update the actions section in the definition,
there’s no need to refresh the workshop to use the updated action;
it’s available immediately.
For a quick reference of the actions in your workshop, run workshop actions:
$ workshop actions dev
This mechanism avoids the need to maintain helper scripts manually, ensuring instead that they are stored with the rest of the workshop’s metadata.
Origins and locations¶
Workshop components, including the many SDK types, originate from different sources and end up in multiple locations. The workshop definition file acts as a blueprint that brings these distributed components together:
Component |
Origin |
Storage location |
Description |
|---|---|---|---|
Created manually in YAML by Workshop users |
Project directory on the host |
Defines the workshop environment and how it should be built and run. |
|
Built into Workshop |
Automatically exposed in the workshop at launch |
Provides host system integration capabilities (mounts, camera, GPU, networking, and so on). |
|
Distributed via the SDK Store
with |
Downloaded, cached on the host, and installed in the workshop at launch |
These SDKs are the most common variation, providing tools and libraries from external publishers. |
|
Created manually or ejected with workshop sketch-sdk --eject |
Defined in the project directory on the host; installed in the workshop at launch |
Custom SDKs, specific to the workshop;
these are defined within the project directory
and can be identified by the |
|
Generated with workshop sketch-sdk |
Defined under |
Encapsulates local, transient logic in an SDK that can be quickly iterated upon and later ejected as an in-project SDK. |
|
Defined by Workshop users |
Listed directly in the workshop definition |
Utility scripts, specific to the workshop; these are injected into the workshop at run time. |
See also¶
Explanation:
How-to guides:
Reference: