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:

workshop.yaml
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:

  1. The base OS is installed.

  2. The system SDK is installed, and its setup-base hook is run.

  3. A ZFS snapshot is taken, and cloned to create a new ZFS file system.

  4. For each subsequent SDK in the order of their appearance on the sdks list, its setup-base hook 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.yaml file. These are the standard capabilities the SDK offers. For Store SDKs, the sdk.yaml file is generated by SDKcraft; plugs and slots are copied as-is from sdkcraft.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.yaml file. 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:

.workshop/dev.yaml
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 connections section 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:

.workshop/dev.yaml
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

Workshop definition

Created manually in YAML by Workshop users

Project directory on the host

Defines the workshop environment and how it should be built and run.

System SDK

Built into Workshop

Automatically exposed in the workshop at launch

Provides host system integration capabilities (mounts, camera, GPU, networking, and so on).

Regular SDKs

Distributed via the SDK Store with channel versioning

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.

In-project SDKs

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 project- prefix in their names in the workshop definition.

Sketch SDK

Generated with workshop sketch-sdk

Defined under $XDG_DATA_HOME/workshop/; installed in the workshop at refresh

Encapsulates local, transient logic in an SDK that can be quickly iterated upon and later ejected as an in-project SDK.

Actions

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: