A snap is a bundle of an app and its dependencies that works without modification across many different Linux distributions. Snaps are discoverable and installable from the Snap Store, an app store with an audience of millions.
Snapcraft is a powerful and easy to use command line tool for building snaps. It reads a simple, declarative file and runs the build for us.
In this tutorial, we’re going to explore some of snapcraft’s best features before using it to create an ideal first snap. For a more detailed look at building snaps, see Creating a snap in the official documentation.
What you’ll learn
In this tutorial, we’ll cover how to: - install the snapcraft tool - create a new project - declare snap metadata - use parts to define an app - build a snap - fix common build issues - upload a snap to the Snap Store
What you’ll need
- Ubuntu 20.04.1 LTS (Focal Fossa), or later, or a derivative
- running from a nested VM requires accelerated/nested VM functionality
- basic command line knowledge and how to edit a file
- rudimentary knowledge of snaps
For an introduction to snaps, and how to use them, take a look at Getting started.
How will you use this tutorial?
- Only read through it
- Read it and complete the exercises
What is your current level of experience?
Originally authored by Gerry Boland.
This tutorial has been written to work on Ubuntu 20.04 LTS. However, it should work without modification on later Ubuntu releases and other GNU/Linux distributions derived from an Ubuntu 18.04+ base, such as Linux Mint 19.x.
First, open up a terminal and make sure you have snap installed:
$ snap version
If it’s installed, you’ll see something similar to the following:
snap 2.49 snapd 2.49 series 16 ubuntu 20.04 kernel 5.4.0-62-generic
See Installing snapd if snap isn’t installed.
We can now install Snapcraft with a single command:
$ sudo snap install --classic snapcraft
ⓘ NOTE: The
--classicswitch enables the installation of a snap that uses classic confinement. We discuss snap security confinement in the following section.
We’re all set. Let’s get cracking and build our first snap!
Building a snap is easy
Starting the project
The first thing to do is to create a general snaps directory followed by a working directory for this specific snap project:
$ mkdir -p ~/mysnaps/hello $ cd ~/mysnaps/hello
It is from within this
hello directory where we will invoke all subsequent commands.
ⓘ NOTE: Due to a limitation in the project we’re going to build, the path of the directory you put the
hellodirectory in shouldn’t contain any spaces.
Get started by initialising your snap environment:
$ snapcraft init
This creates a
snapcraft.yaml in which you declare how the snap is built and which properties it exposes to the user. We will edit this later.
The directory structure now looks like this:
mysnaps/ └── hello └── snap └── snapcraft.yaml
ⓘ Note: Any future snaps you want to create should be put within their own directory under
Describing the snap
Let’s take a look at the top part of your snapcraft.yaml file. It should look somewhat as shown below:
name: my-snap-name # you probably want to 'snapcraft register <name>' base: core18 # the base snap is the execution environment for this snap version: '0.1' # just for humans, typically '1.2+git' or '1.3.2' summary: Single-line elevator pitch for your amazing snap # 79 char long summary description: | This is my-snap's description. You have a paragraph or two to tell the most important story about your snap. Keep it under 100 words though, we live in tweetspace and your description wants to look good in the snap store. grade: devel # must be 'stable' to release into candidate/stable channels confinement: devmode # use 'strict' once you have the right plugs and slots
This part of
snapcraft.yaml is mandatory and is basic metadata for the snap.
Let’s go through this line by line:
name: The name of the snap.
base: A foundation snap that provides a run-time environment with a minimal set of libraries that are common to most applications. The template defaults to using
core18, which equates to Ubuntu 18.04 LTS. See Base snaps for further options.
version: The current version of the snap. This is just a human readable string. All snap uploads will get an incremental snap revision, which is independent from this version. It’s separated so that you can upload multiple times the same snap for the same architecture with the same version. See it as a string that indicates to your user the current version, like “stable”, “2.0”, etc.
summary: A short, one-line summary or tag-line for your snap.
description: A longer description of the snap. It can span over multiple lines if prefixed with the ‘|’ character.
grade: Can be used by the publisher to indicate the quality confidence in the build. The store will prevent publishing ‘devel’ grade builds to the ‘stable’ channel.
confinement: A snap’s confinement level is the degree of isolation it has from your system, and there are three levels:
devmode. strict snaps run in complete isolation,
classicsnaps have open access to system resources and devmode snaps run as strict but with open access to the system. The latter is ideal for development, but your snap will need move from devmode to be published. See Snap confinement for more details.
In this tutorial, we will focus on
For more detailed information on this top-level metadata, see Adding global metadata.
And that’s it for the basics. It’s now time to customise the snapcraft.yaml file for our own snap. Taking the above into account, we can change the top of the file to be:
name: hello base: core18 version: '2.10' summary: GNU Hello, the "hello world" snap description: | GNU hello prints a friendly greeting. grade: devel confinement: devmode
ⓘ Note: Version information is for snap user consumption only, and has no effect on snap updates. It’s defined within quotes, (
'2.10'), because it needs to be a YAML string rather than a floating-point number. Using a string allows for non-numeric version details, such as ‘
myfirstversion’ or ‘
Adding a part
Parts are used to describe your application, where its various components can be found, its build and run-time requirements, and those of its dependencies. A snap consists of one or more parts, depending on its complexity.
Here are a few multiple-part snap examples:
- snaps with separate logical parts, such as a server snap containing a web server, a database and the application itself
- a game which ships the game engine and game data for three different games, each one being defined in its own part
- snaps with parts from different locations - parts which are built in a different way
hello snap will be nice and simple. It will consist of only one part for now. In the following pages we are going to gradually extend it.
Two must-haves for every part are the ‘source’ and ‘plugin’ definition. Think of these as the “what” and the “how”, respectively. As source you can, for example, pick a source repository (like
git), a tarball, or a local directory. Snapcraft supports many plugins, allowing you to build a wide variety of project types (e.g. autotools, cmake, go, maven, nodejs, python2, python3).
hello, add the following ‘parts’ stanza to your
snapcraft.yaml file (replace anything else that might be there):
parts: gnu-hello: source: http://ftp.gnu.org/gnu/hello/hello-2.10.tar.gz plugin: autotools
So we have added a part called
gnu-hello (its name is arbitrary). For ‘source’, we specified a tarball located on the GNU project’s FTP server. As ‘plugin’ we’ve chosen
autotools which uses the traditional
./configure && make && make install build steps.
See Supported plugins, or run
snapcraft list-plugins, to get more information on which build-tools and platforms Snapcraft supports.
To build our snap all you need to do is:
The first time you run snapcraft, you may be asked for permission to install Multipass. Snapcraft uses Multipass to both simplify the build process and to confine the build environment within a virtual machine. It offers the best build experience, so we highly recommend answering ‘y’. However, if you’d rather not use Multipass, you can also build natively, remotely, and with LXD. See Build options for details.
During the build, snapcraft will show plenty of output, however a successful build will end with:
[...] Staging gnu-hello + snapcraftctl stage Priming gnu-hello + snapcraftctl prime Snapping | Snapped hello_2.10_amd64.snap
Congratulations! You’ve just built your first snap, which is now ready to be installed:
$ sudo snap install --devmode hello_2.10_amd64.snap
The output should declare:
hello 2.10 installed
To get some info on the installed snap:
$ snap list hello
Name Version Rev Tracking Publisher Notes hello 2.10 x1 - - devmode
Let’s try to execute it:
On traditional Ubuntu you will get:
The program 'hello' can be found in the following packages: * hello * hello-traditional Try: sudo apt install <selected package>
Or you might get a different error if you previously installed the
bash: /snap/bin/hello No such file
The command doesn’t exist despite being part of our snap and installed! Indeed, snaps don’t expose anything to the user by default (command, services, etc.). We have to do this explicitly and that’s exactly what you are going to tackle next!
ⓘ If it does work for you, you should verify that it’s the correct
hellocommand. Check the output of
which hello- it might list something like
/usr/bin/hello. What we’re after is a binary under the
Exposing an app via your snap!
In order for services and commands to be exposed to users, you need to specify them in
snapcraft.yaml of course! This will take care of a couple of things for you:
- it will make sure that services are automatically started/stopped
- all commands will be “namespaced”, so that you could, for example, install the same snap from different publishers and still be able to run the snaps separately
hello command is painless. All you need to do is add the following to your
apps: hello: command: bin/hello
This defines an app named
hello, which points to the executable
bin/hello in the directory structure shipped by the snap.
We generally advise to put this stanza between the metadata fields and the ‘parts’ field. Technically the order doesn’t matter, but it makes sense to place basic pieces before more complex ones.
snapcraft.yaml file should now resemble this:
name: hello base: core18 version: '2.10' summary: GNU Hello, the "hello world" snap description: | GNU hello prints a friendly greeting. grade: devel confinement: devmode apps: hello: command: bin/hello parts: gnu-hello: source: http://ftp.gnu.org/gnu/hello/hello-2.10.tar.gz plugin: autotools
Iterating over your snap
Now that the command is defined, let’s rebuild the snap. You can do do this by simply running snapcraft again - only the new or changed elements will be built and merged into a new snap.
However, to show a more typical snap-building process, we’re going to use a slightly different command that will allow us to peek into the snap we’re building before the snap is created:
$ snapcraft prime --shell
This command tells snapcraft to run the build up until the “prime” step and open a shell within the snap build environment, with prime being the final stage in a four step process:
- pull: downloads or otherwise retrieves the components needed to build the part
- build: constructs the part from the previously pulled components
- copies the built components into the staging area
- copies the staged components into the priming area, to their final locations for the resulting snap.
Another useful command is
snapcraft --debug. This will open a shell in the build environment when an error occurs, letting you investigate the error before resuming the build.
From within the shell, you can see that while the binary we just added is in the stage directory, it’s not yet in prime:
$ ls stage/bin/ hello $ ls prime/bin/ ls: cannot access 'prime/bin': No such file or directory
You can continue building your snap from within the build environment using the same snapcraft commands you use outside, with the convenience of having a prompt directly within the environment. To build the prime stage, for example, just type
$ snapcraft prime Skipping pull gnu-hello (already ran) Skipping build gnu-hello (already ran) Skipping stage gnu-hello (already ran) Priming gnu-hello + snapcraftctl prime
The hello binary will now be in the prime/bin directory:
$ ls prime/bin/ hello
To resume the build and generate the snap, you can type
snapcraft within the build environment, or exit and run
snapcraft again from there. Either way, the resultant snap will be placed in the snap project directory.
We can now re-install the new snap and run hello:
$ sudo snap install --devmode hello_2.10_amd64.snap $ hello Hello, world!
The path for the binary should be correctly set too:
$ which hello /snap/bin/hello
Well done! You’ve just made your first working snap!
A snap is made of parts
Let’s add another part to make the snap a bit more interesting. In the ‘parts’ definition, make an addition:
parts: [...] gnu-bash: source: http://ftp.gnu.org/gnu/bash/bash-4.3.tar.gz plugin: autotools
You will notice that this part (named
gnu-bash) works very much like the
gnu-hello part from before: it downloads a tarball and builds it using the
As we did before, we need to define the command we want to expose. Let’s do this now. In the ‘apps’ definition, add:
apps: [...] bash: command: bash
This time the command name is different from the snap name. By default, all commands are exposed to the user as
<snap-name>.<command-name>. This binary will thus be
hello.bash. That way, we will avoid a clash with
/bin/bash (system binaries trump binaries shipped by snaps) or any other snaps shipping a
bash command. However, as you may remember, the first binary is named
hello. This is due to the simplification when equals . Instead of
hello.hello, we have the command condensed to
Our snap will thus result in two binaries being shipped:
Note that we set
bash as the command parameter, and not
bin/bash relative to the system snap directory (
$SNAP=/snap/hello/current) as we did for
hello. Both are equally valid because
snapd create a small wrapper around your executable command which sets some environment variables. Technically,
$SNAP/bin will be prepended to your
$PATH for this snap. This avoids the need to set the path explicitly. This topic will be touched upon in upcoming sections.
Now re-do the build:
gnu-bash part will be built now (as nothing changed in the other part). This makes things quicker but since Bash is itself a significant piece of software this command will still take quite some time to complete.
Install the resulting snap again and check whether the new binary is available:
$ sudo snap install --devmode hello_2.10_amd64.snap $ hello Hello, world!
Now try bash:
The above should yield:
bash-4.3$ env [ outputs a list of environment variables ]
Now exit that Bash shell:
You will see that the environment variables available from your snap are a little different from your user environment. Some additional variables are added like
$SNAP_ and some system environment variables have been altered to point to your snap directory, like
$LD_LIBRARY_PATH. Take the time to get familiar with them!
See Environment variables for further details.
Excellent work! You have it all nice and working!
One last thing you might want to do before the snap is ready for wider consumption is to remove the
Users of snaps using
devmodewill need to pass
--devmodeduring the installation, so they explicitly agree to trust you and your snap. Another benefit of removing
devmodeis that you will be able to ship your snap on the ‘stable’ or ‘candidate’ channels (you can only release to the other channels, like ‘beta’ or ‘edge’ as your snap is less trusted) and users will be able to search for it using
For this to be declared in your snap, let’s set
Now let’s build the snap and install it properly! That is, we are going to call
--devmode to really test it under confinement:
$ snapcraft [...] $ sudo snap install hello_2.10_amd64.snap
Yikes! This gives:
error: cannot find signatures with metadata for snap "hello_2.10_amd64.snap"
Indeed, we tried to install a snap that wasn’t signed by the Snap Store. Previously, we performed local installations via
--devmode which implied (in addition to being run without confinement) that an unsigned snap was OK to be installed. As this is not the case any more we need to indicate that it’s OK to install an unsigned snap. This is done via the
$ sudo snap install hello_2.10_amd64.snap --dangerous
$ hello Hello, world!
Creating a new shell
…and issue a command there:
ls: cannot open directory '.': Permission denied
Exit the shell for now:
What’s happening here? Your snap is not broken, it’s just confined now and so it can only access its own respective directories.
For other snaps you might need to declare if commands or services need special permissions (e.g. access to the network or audio). A tutorial on “interfaces”, “slots”, and “plugs” will cover this very topic.
You are done. This snap is ready for publication. Awesome!
Push to the store
Applications are easily uploaded to the Snap Store. Registering an account is easy, so let’s do that first.
Registering an account
Begin by going to the Snapcraft dashboard and clicking on the “Sign in or register” button in the top-right corner:
If you do not already have an Ubuntu One (SSO) account then select “I am a new Ubuntu One user” and complete the needed data:
Once logged into Ubuntu One you will see your name in the top-right corner. Click your name to reveal a menu and then choose “Account details”. You will need to agree to the Developer Terms and Conditions before clicking the green “Sign up” button:
Your current settings will be displayed. Review them. Your “Snap store username” may be preset and non-editable. There are “Contact details” you may wish to fill out as well as a personal photo to upload.
If you made any changes, press the green “Update my account” button.
We’ll now log in with the snapcraft command using your new account. The first time you do so you will be asked to enable multi-factor authentication and agree with the developer terms & conditions:
$ snapcraft login
A sample session follows:
Enter your Ubuntu One e-mail address and password. If you do not have an Ubuntu One account, you can create one at https://snapcraft.io/account Email: email@example.com Password: Second-factor auth: (press Enter if you don't use two-factor authentication): Login successful.
You can log out any time with
Register a snap name
Before being able to upload a snap, you will need to register (reserve) a name for it. This is done with
snapcraft register <some_name>.
Here, assuming javier is the store username established above, we’ll do:
$ snapcraft register javier-hello
A sample session follows:
We always want to ensure that users get the software they expect for a particular name. If needed, we will rename snaps to ensure that a particular name reflects the software most widely expected by our community. For example, most people would expect 'thunderbird' to be published by Mozilla. They would also expect to be able to get other snaps of Thunderbird as 'thunderbird-$username'. Would you say that MOST users will expect 'javier-hello' to come from You, and be the software you intend to publish there? [y/N]: y Registering javier-hello. Congratulations! You're now the publisher for 'javier-hello'.
Clearly, the Store prefers the name to be of the format
<local snap name>-<store-username>.
The snap name
javier-hello is different from
hello that we initially placed in our
snapcraft.yaml file. We will need to edit that file accordingly and rebuild the snap. This is also an opportune time to change the ‘grade’ to ‘stable’!
The file should now include the following lines:
name: javier-hello grade: stable
You should now have a snap package called
Recall that you already installed a snap package called
hello_2.10_amd64.snap. Don’t forget to uninstall it with
sudo snap remove hello.
Push and release your snap
It’s time to make this snap available to the world!
Let’s release it to the ‘candidate’ channel for now:
$ snapcraft push javier-hello_2.10_amd64.snap --release=candidate
Pushing javier-hello_2.10_amd64.snap After pushing, an attempt will be made to release to 'candidate' Preparing to push '/home/ubuntu/mysnaps/hello/javier-hello_2.10_amd64.snap' to the store. Pushing javier-hello_2.10_amd64.snap [=====================================================] 100% Processing...| Ready to release! Revision 1 of 'javier-hello' created. Track Arch Channel Version Revision latest amd64 stable - - candidate 2.10 1 beta ^ ^ edge ^ ^ The 'candidate' channel is now open.
You should receive an email informing you that your snap is pending review (automatic checking). If you are not using any reserved interfaces and security checks are passing, users will be able to install it like so:
$ sudo snap install javier-hello --channel=candidate
As we uploaded an amd64 binary, only people on 64-bit machines will get access to this snap. You can either focus on one architecture to support, manually build a binary for each architecture you wish to support, or use remote build or build.snapcraft.io to push your
snapcraft.yaml, and get resulting snaps built on all architectures for you!
See Build options for more details.
From here, if you are happy with the testing of your snap, you can use the
snapcraft release command to have fine-grained control over what you are releasing and where:
snapcraft release <snap-name> <revision> <channel>
Therefore, to release
javier-hello to the ‘stable’ channel, and make it immediately visible in the Store:
$ snapcraft release javier-hello 1 stable
Remember that snaps with
confinement: devmode can’t be released to the ‘stable’ or ‘candidate’ channels.
The web interface will give you information about the publication status. Take a look to see all the available options!
That’s all folks!
Easy, wasn’t it?
Congratulations! You made it!
By now you will have built your first snap, fixed build issues, exposed user commands, learned about uploading snaps to the Snap Store, and found out about a lot of other useful details (plugins, snapcraft help, channels, etc.).
For reference, the final state of your snapcraft.yaml file should be as follows:
name: hello base: core18 version: '2.10' summary: GNU Hello, the "hello world" snap description: | GNU hello prints a friendly greeting. grade: stable confinement: strict apps: hello: command: bin/hello bash: command: bash parts: gnu-hello: source: http://ftp.gnu.org/gnu/hello/hello-2.10.tar.gz plugin: autotools gnu-bash: source: http://ftp.gnu.org/gnu/bash/bash-4.3.tar.gz plugin: autotools
- Take a look at tutorial Build a nodejs service snap. It is the logical follow-up to this tutorial. It includes debugging techniques, more information on confinement, and how to package a snap as a service.
- Learn some advanced snap coding techniques by looking at some of the other snap tutorials.
- Join the Snapcraft community on the Snapcraft forum.