How SDKs compare to Dockerfiles¶
Workshop didn’t occur in a vacuum; there have been many attempts to provide developers with robust environments. A common approach is to use Docker to achieve repeatability, persistence, layering, and various other benefits that the technology offers.
We won’t dwell on the pros and cons here; instead, let’s discuss how a typical Dockerfile development environment maps to a workshop and its SDKs.
Note
We assume you’re familiar with SDKcraft basics covered in the Craft SDKs with SDKcraft tutorial section and have an understanding of Docker.
Feature discussion¶
To begin with, it’s perfectly reasonable to draw a few comparisons between Docker and the combination of Workshop and SDKcraft.
(Im)mutability ¶
The first contrast comes from the overall approach: Docker images are conceived to be immutable, whereas workshops are designed to evolve over time. This affects all aspects of their design and implementation, including how Dockerfiles and SDKs are laid out, respectively.
Bind mounts and volumes¶
Docker provides several ways to manage data persistence and storage
such as the VOLUME instructions,
the docker volume command
or the --mount and -v options in docker run.
The expectations for their configuration are set by the image author
but the actual parameters are provided by users at the author’s guidance;
the resulting manual process is error-prone and adds unnecessary overhead.
Workshop and SDKcraft reciprocate this with mount interface plugs that are akin to Docker volumes and the workshop remount command that enables remounting existing plugs to a given location. However, the user can’t create arbitrary mounts; the choice is limited to what the SDKs offer.
In turn, this implies that the mount logic in Workshop and SDKcraft is built into the SDK by its author, not implemented manually by the user; unless the user decides to intervene, the mounts are managed automatically and largely stay hidden.
Resource usage¶
For largely historical reasons, the Docker way of accessing various host resources can be notably inconsistent; for example, enabling GPU pass-through is visibly different from SSH forwarding.
In contrast, Workshop and SDKcraft unify these mechanisms under the single concept of an interface, providing a consistent way to uniformly manage host resource access.
Parts and layers¶
Docker relies on a temporally layered approach, where each change is built on top of the previous one.
Our SDKs are structured using parts;
their expressiveness makes them more diverse and semantically rich,
allowing the layout of an SDK to be formalized in a modular way.
If necessary, the layered approach
can be mimicked using SDK hooks.
Workshop uses ZFS snapshots and clones
to cache the results of each setup-base hook.
Build commands¶
In Docker,
build commands are typically bundled as RUN instructions.
In SDKcraft SDKs,
the setup-base hook
is responsible for building the workshop,
but other hooks add extra functionality with runtime events and health checks.
A related Docker pattern applies USER
to switch from root to a nonroot user,
followed by RUN instructions for user-level setup:
# System setup as root (≈ setup-base)
RUN apt-get update && apt-get install -y ...
# Switch to non-root user and set up the project (≈ setup-project)
USER appuser
WORKDIR /home/appuser
RUN pip install --user ...
In Workshop,
this maps to the setup-project hook,
which inherently runs as the workshop user.
Unlike setup-base,
setup-project runs after interfaces are connected
and the /project/ directory is mounted,
so it can leverage available hardware
and install project-specific dependencies.
Data persistence and sharing¶
Consider this Docker command:
$ docker run --name share-example --entrypoint bash -it \
-v ~/docker/kit/cache/Kit:/kit/cache:rw \
-v ~/docker/cache/ov:/root/.cache/ov:rw \
...
All too familiar, isn’t it? When running a sufficiently complex container, you need to mount a lot of directories to make it work, and the handling of these mounts both inside and outside the container can quickly become an overhead.
Workshop addresses this issue by providing a way to reuse and share content between the host and the workshop via SDKs while keeping manual intervention to a necessary minimum. Typically, workshops are isolated from each other and from the host system; all data exchange is via the mount interface.
To use this interface, your SDK defines a mount interface plug. When a workshop uses the SDK, an auto-assigned, noncustomizable source directory on the host is mounted to the plug-defined target directory inside the workshop. What’s more, its contents are preserved during refresh operations. In this way, Workshop enables SDK data persistence and reuse inside individual workshops.
Note, however, that files created in the plug’s target location by any means will only be accessible to the workshop to which that specific auto-assigned source directory is mounted to. Other workshops, even if they use the same SDK, cannot access these files and will not share them; their source directories will be different.
Persistence and reuse between workshops¶
This is the simplest scenario;
you use the mount interface
to define the target directory
where the content will be mounted inside the workshop
per each directory you want to retain during the workshop’s lifecycle.
name: data-science
title: Data science SDK
base: ubuntu@22.04
summary: This SDK does some data science.
description: |
Besides doing actual data science,
this SDK demonstrates content sharing and persistence between workshops
by enabling two plugs that can store reusable data specific to the SDK.
plugs:
share-cache:
interface: mount
workshop-target: /opt/cache
training-data:
interface: mount
workshop-target: /opt/training
read-only: true
This SDK defines two mount plugs;
for each,
Workshop creates a source directory on the host at runtime.
Both workshop-target directories inside the workshop
can be used by the SDK-specific logic
implemented via hooks and other features.
Additionally, you can mark a directory as read-only. Workshop will then enforce the immutability of resources in this directory when they are accessed from inside the workshop.
Here’s a corresponding workshop definition:
name: data
base: ubuntu@22.04
sdks:
- name: data-science
The default host location that Workshop mounts to the target is predefined as follows:
$XDG_DATA_HOME/workshop/id/<PROJECT ID>/<WORKSHOP>/mount/<SDK>/<PLUG>/
In the above example,
this would be
~/.local/share/workshop/id/<PROJECT ID>/<WORKSHOP>/mount/data-science/share-cache/.
In particular,
this means that the SDK’s plug in each workshop
will have its own unique source directory.
Feature mapping¶
Any attempt at a straightforward comparison of these different, albeit vaguely similar, technologies is mostly futile. Again, a key difference is that a Dockerfile is controlled by the user, but a workshop is managed by the user, yet it relies on publisher-defined SDKs whose layout is beyond the user’s reach.
This means that some capabilities of Docker won’t be available to a user of Workshop alone, so the functionality is split between the user-oriented Workshop and the publisher-focused SDKcraft.
Important Dockerfile instructions are mapped to SDKcraft as follows:
Dockerfile |
SDKcraft |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
In turn, the CLI subcommands can be mapped like this:
Docker CLI |
Workshop/SDKcraft CLI |
|---|---|
docker build |
sdkcraft build, sdkcraft pack |
docker exec |
workshop exec, workshop shell, workshop run |
docker images, docker ps |
workshop info, workshop list |
docker logs |
workshop changes, workshop tasks |
docker rm, docker rmi |
workshop remove |
docker run |
workshop launch, workshop refresh |
docker run --mount, docker volume |
workshop remount |
docker start |
workshop start |
docker stop |
workshop stop |
Case study: ROS 2¶
For a specific example, consider the Docker-based tutorial for ROS 2, the open-source robotics operating system. The choice is influenced by many factors, including the fact that we have a ROS 2 SDK available for comparison; for details, refer to the corresponding how-to guide under See also.
Nonetheless, we won’t focus on the specifics of ROS 2 here; instead, we discuss how certain parts of an arbitrarily sophisticated Dockerfile map to a similar SDK and the workshop that uses it.
Base image¶
The example suggests using the ros:rolling tag for the
Dockerfile;
with a few levels of indirection,
it comes down to this (or similar) instruction:
FROM ubuntu:noble
For Workshop and SDKcraft,
this translates to ubuntu@24.04
in the SDK definition
and the workshop definition.
Project workspace¶
The project workspace in the example is defined as a bind mount that eventually becomes this:
$ docker run -it \
--mount type=bind,source=/home/user/ros-project,target=/home/ws/src,consistency=cached \
# ...
Its counterpart in Workshop is the project directory
where the workshop was defined and launched;
it is automatically mounted as /project/ when the workshop is started:
$ workshop launch ros2jazzy # must be run in the project directory
No explicit configuration is needed; this behavior is intentionally consistent across all workshops.
Bind mounts¶
The ROS 2 example defines a few more mounts; a complete docker run command may look like this:
$ docker run -it \
--name ros2_container \
--mount type=bind,source=/home/user/ros-project,target=/home/ws/src,consistency=cached \
--mount type=bind,source=/home/user/.ros,target=/root/.ros,consistency=cached \
--mount type=bind,source=/tmp/.X11-unix,target=/tmp/.X11-unix,consistency=cached \
--mount type=bind,source=/dev/dri,target=/dev/dri,consistency=cached \
ros2
In Workshop and SDKcraft, additional filesystem mounts are defined by the SDK author or the user using the mount interface:
plugs:
ros-cache:
interface: mount
workshop-target: /home/workshop/.ros
# ...
Just like with the project files, this avoids the need for manual setup when starting the workshop:
$ workshop launch ros2jazzy # the plugs are mounted automatically
Again, Workshop and SDKcraft have no direct counterpart to bind mounts; plugs are more similar to Docker volumes. Yet, the workshop remount command enables remounting existing plugs to new host directories:
$ workshop remount ros2jazzy/ros2:ros-cache ~/new-cache-mount/
Thus, Workshop and SDKcraft largely leave the design of mount points to the SDK author, allowing the user to rely on their default, well-defined behavior with the extra option of adjusting them if necessary.
Build commands¶
Normally, a RUN instruction in a Dockerfile
translates to the setup-base and setup-project
hooks in an SDK pretty well.
Here, the steps to
set up keys,
then configure the repos
and install the packages
largely stay the same.
However, setup-project runs with the project directory already mounted,
so any steps that rely on the contents of the project itself
can be implemented with the same hook.
In particular, this enables the ROS 2 SDK
to transparently identify and install project-specific dependencies.
See also¶
Explanation:
Reference: