If you’ve ever built a snap with Snapcraft or a rock with Rockcraft, you’ve likely engaged with the documentation and forums along your path to a working artifact. We put a lot of time into providing means for developers to learn how to package their software. But we’ve put comparatively little time into describing the structure of the craft apps themselves.
In this post, I’d like to shed some light on the philosophy across the growing list of Canonical packaging tools, including Snapcraft, Charmcraft, and Rockcraft. When we (or you, prospective contributor!) are developing these craft apps or bootstrapping new ones, it’s helpful to know what these principles are. Applying these will lead to a better experience for package maintainers, especially for maintainers who use multiple craft apps. And even if you don’t plan to become a craft contributor yourself, you may still find that these ideas give you a better intuition for how to get things done when debugging your builds.
The craft principles
Declarative
A craft app translates a high-level description of what the packaged software is into the low-level steps of how to build it.
For a package maintainer, the artifact is defined by the craft’s YAML project file. This file is like a list of ingredients, in that it describes what the finished artifact should contain. In practical terms, the file itemizes the software components and resources to build and include, and internal properties and structures like the file layout. The maintainer describes the result, and doesn’t have to provide the craft with step-by-step instructions for how to accomplish it.
The project file can represent most artifacts fully declaratively. Inevitably, there will be instances when a particular package requires custom build logic. In these cases, package maintainers can insert custom code blocks of shell commands. Nevertheless, it’s our intention that craft app developers should rarely resort to procedural code.
By making the build specifics abstract, package maintainers can use the same syntax across many types of build artifacts. Many keys and sections are in fact identical across the craft apps.
Standardized
Using the project file as configuration, a craft app runs a consistent set of phases to generate a fully-formed artifact. This happens in the background by default, but if the process fails, the developer can drop into the incomplete artifact, inspect what’s going on, and debug it.
Craft apps recognize that all artifact creation involves the same high-level steps: downloading source code, running build systems to generate libraries or executables, and rearranging the resulting files. Each craft app follows the same build sequence and has the same working file tree. A craft app builds artifacts by moving linearly through up to five lifecycle stages: pull, overlay, build, stage, and prime. A package maintainer can run the full process, or run everything up to a specific step. Files migrate from one stage to another in customizable but predictable ways.
Internally, craft apps follow a common object-oriented architecture. An individual craft app only needs logic specific to its output artifact. Any functionality that is shared across crafts, like provisioning build environments or parsing command-line arguments, is handled through shared craft libraries. This common structure allows parts to be combined flexibly regardless of toolchain, and keeps the lifecycle paradigm consistent regardless of output format. Moreover, the commands and status output from a craft app will look immediately familiar to a package maintainer using a different craft app.
Modular
A craft app builds artifacts through composable pieces. It encourages developers to create more such pieces that work across craft apps.
Craft plugins encapsulate common development patterns and standardize disparate language ecosystems. While the current set of plugins focus on programming languages and build systems, any set of files you can generate from some commands can be represented as a plugin. For example, in a craft app like Imagecraft that builds disk images, one could create a plugin for common bootloaders or ramdisk formats.
A plugin guides the package maintainer towards a recommended way to use a particular language or toolchain. For example, some language plugins can install a compiler or interpreter by simply specifying a version as a parameter. However, plugins do not restrict how a package maintainer may use the language or toolchain. Those same language plugins work equally well with a preexisting interpreter installed by a different part or already present in the build environment.
This flexibility lets the package maintainer customize and optimize build processes to their needs. By providing a plugin, the craft app developer can save a package maintainer from learning arcane build configuration and ecosystem-specific dependency management.
Reproducible
A host’s environment should not influence the artifact that a craft app generates.
A craft app sets up isolated, pristine environments for building and packaging, in the form of containers or virtual machines. A command to drop into a shell within the managed environment facilitates debugging.
By isolating their build environment, a package maintainer is reassured that the build recipe represents a complete and valid process for creating the artifact.
How we put these principles into action
To recap, when developing craft libraries and tools, we:
-
Provide a declarative mechanism for the package maintainer to express properties of their build and artifact.
-
Make processes for building and packaging modular and reusable.
-
Use the standardized phases and folder structure provided by the craft libraries.
-
Keep reliable and consistent build output.
Let’s now look at an example where these ideas guided how we designed a craft user experience.
Recently, the Starcraft team has developed Imagecraft for creating bootable images of the sort used for cloud, desktop, and embedded devices. One key component of any viable Ubuntu disk image is a root filesystem, which we can generate using the mmdebstrap utility. The following YAML block would work in Imagecraft, or any other craft app for that matter, to create a root filesystem:
parts:
rootfs:
plugin: nil
build-packages: ["mmdebstrap"]
override-build: |
mmdebstrap --arch $CRAFT_ARCH_BUILD_FOR \
--mode=sudo \
--format=dir \
--variant=minbase \
--include=apt \
noble \
$CRAFT_PART_INSTALL/ \
http://archive.ubuntu.com/ubuntu/
rm -r $CRAFT_PART_INSTALL/dev/*
organize:
'*': (overlay)/
We didn’t need to do anything special to run this filesystem bootstrapping process. But, we can wrap this functionality into an Imagecraft plugin. The resulting project file section is considerably more modular, declarative, and standardized:
parts:
rootfs:
plugin: mmdebstrap
mmdebstrap-variant: minbase
mmdebstrap-packages: [apt]
organize:
'*': (overlay)/
Observe how we applied the earlier principles:
-
We made available a modular plugin called mmdebstrap, implemented elsewhere within the Imagecraft codebase, to encapsulate the command for reuse across images.
-
We introduced a declarative set of
mmdebstrap-*properties, instead of running a list of commands. -
We transparently standardized the output location into a place that later parts of the image build process can use.
Together, these decisions make the image building process more approachable. The user has less boilerplate code to copy, and is only exposed to settings that make sense to customize.
The future
We’re consistently using the principles described here as we build new craft apps, including Imagecraft for bootable images and Debcraft for traditional Debian packages. These principles have emerged over years of building snaps, rocks, and charms. We believe they apply equally well to other formats.
Through modularity and standardization, we’re retiring old collections of shell scripts across disjoint tools in favor of stable and well-tested processes. As our supported architectures and build infrastructure grow, the declarative and reproducible nature of the craft apps will serve us well.
For a more user-centric discussion of how and why the current set of craft apps work the way they do, read @jnsgruk’s July 2025 blog post ”Crafting Your Software”.