Create your first snap

1. Overview

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.

IMAGE

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 24.04 LTS (Noble Numbat), 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?

What is your current level of experience?

Originally authored by Gerry Boland.


2. Getting started

This tutorial has been written to work on Ubuntu 24.04 LTS. However, it should work without modification on later Ubuntu releases and other GNU/Linux distributions derived from an Ubuntu 14.04+ base, such as Linux Mint 22.x.

Installing dependencies

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.63
snapd   2.63
series  16
ubuntu  24.04
kernel  6.8.0-35-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 --classic switch 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!


3. 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 hello directory 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 mysnaps.

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: core22 # 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

parts:
  my-part:
    # See 'snapcraft plugins'
    plugin: nil

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 core22, which equates to Ubuntu 24.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: strict, classic and devmode. strict snaps run in complete isolation, classic snaps 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 devmode and strict confinement.

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: core22
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 ‘2.3-git’.

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

Our 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).

To build 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:

$ snapcraft

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 progress messages, and a successful build will end with:

Generated snap metadata                                                        
Created snap package hello_2.10_amd64.snap

The full output from the snapcraft execution can be found in ~/.local/state/snapcraft/log/snapcraft-<date-time>.log

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

Sample output:

Name   Version  Rev  Tracking  Publisher  Notes
hello  2.10     x1   -         -          devmode

Let’s try to execute it:

$ hello

On traditional Ubuntu you will get:

Command 'hello' not found, but can be installed with:
sudo snap install hello              # version 2.10, or
sudo apt  install hello              # version 2.10-3
sudo apt  install hello-traditional  # version 2.10-6
See 'snap info hello' for additional versions.

Or you might get a different error if you previously installed the hello snap:

$ hello

Output:

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 hello command. Check the output of which hello - it might list something like /usr/bin/hello. What we’re after is a binary under the /snap/bin directory.


4. Exposing an app via your snap!

Defining commands

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

Exposing the hello command is painless. All you need to do is add the following to your snapcraft.yaml file:

apps:
  hello:
    command: usr/local/bin/hello

This defines an app named hello, which points to the executable usr/local/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.

Our 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: usr/local/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:

  1. pull: downloads or otherwise retrieves the components needed to build the part
  2. build: constructs the part from the previously pulled components
  3. copies the built components into the staging area
  4. 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.

The shell is started as user root in the /root/project directory, which contains the snapcraft.yaml file.
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:

# pwd
/root/project
# ls ../stage/usr/local/bin/
hello
# ls ../prime/usr/local/bin/
ls: cannot access 'prime/usr/local/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:

# snapcraft prime
Generated snap metadata

The hello binary will now be in the prime/usr/local/bin directory:

# ls ../prime/usr/local/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 2.10 installed
$ 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!


5. 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-5.2.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 autotools plugin.

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: usr/local/bin/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 hello.

Our snap will thus result in two binaries being shipped: hello and hello.bash.

As we did for hello, we must set the command parameter as usr/local/bin/bash, since the bash executable is not built in the default bin directory.

Now re-do the build:

$ snapcraft

Only the 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:

$ hello.bash

The above should yield:

bash-5.2$ env
[ outputs a list of environment variables ]

Now exit that Bash shell:

bash-4.3$ exit

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 $PATH or $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!


6. Removing devmode

One last thing you might want to do before the snap is ready for wider consumption is to remove the devmode status.

Important:
Users of snaps using devmode will need to pass --devmode during the installation, so they explicitly agree to trust you and your snap. Another benefit of removing devmode is 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 snap find.

For this to be declared in your snap, let’s set confinement to strict in snapcraft.yaml:

confinement: strict

Now let’s build the snap and install it properly! That is, we are going to call snapcraft without --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 --dangerous option:

$ sudo snap install hello_2.10_amd64.snap --dangerous

Test again!

$ hello
Hello, world!

Creating a new shell

$ hello.bash

…and issue a command there:

bash-5.2$ ls

now gives:

ls: cannot open directory '.': Permission denied

Exit the shell for now:

bash-5.2$ exit

What’s happening here? Your snap is not broken, it’s just confined now and so it can only access its own respective directories.

Note:
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!


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

IMAGE

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:

IMAGE

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.

Command-line authentication

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 protected]
Password:

Login successful.

You can log out any time with snapcraft logout.

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 '$username-thunderbird'.

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
craft-store error: Store operation failed:
- name-review-required: All snap names are currently subject to a review. Please see https://forum.snapcraft.io/t/manual-review-of-all-new-snap-name-registrations/39440 for details.
Follow this link to submit a name request for your snap: https://dashboard.snapcraft.io/register-snap                                                                         
Full execution log: '/home/javier/.local/state/snapcraft/log/snapcraft-20240702-182035.747932.log'                                                  ```

When you follow the link to register a snap, you will be asked for the name you want to register:
register-name

Until the name has been reviewed, it will show as:

Clearly, the Store prefers the name to be of the format <store-username>-<local snap name>.

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

Rebuild:

$ snapcraft
Generated snap metadata
Created snap package javier-hello_2.10_amd64.snap

You should now have a snap package called javier-hello_2.10_amd64.snap.

Note:
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 upload javier-hello_2.10_amd64.snap --release=candidate

Output:

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 snapcraft.io/build 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!


8. 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: core22
version: '2.10'
summary: GNU Hello, the "hello world" snap
description: |
  GNU hello prints a friendly greeting.
grade: stable
confinement: strict

apps:
  hello:
    command: usr/local/bin/hello
  bash:
    command: usr/local/bin/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-5.2.tar.gz
    plugin: autotools

Next steps

  • 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.

Further readings