Management Snappy Package Manager Lead image: Lead Image © Amawasri Naksakul, 123RF.com
Lead Image © Amawasri Naksakul, 123RF.com
 

Packaging Apps To Run on Any Linux Device

It's a Snap!

Canonical's Snapcraft (Snappy) package manager creates a self-contained application that works across Linux distributions. We show you how to install, publish, and run a simple snap. By Chris Binnie

Those with a smattering of gray hair might be forgiven for mentioning that containers have been around for a relatively long time and that their evolution is not quite as magical as it may seem at first. Docker [1], however, is an undeniably fantastic advancement in the way infrastructure is created and operated. From a DevOps perspective, software deployments are no doubt possible considerably more often than before. It's unlikely in the long-term however that Docker will be the only popular container runtime daemon as rkt [2] and the Open Container Initiative [3] take off.

Since 2014, a major player in the Linux market has been touting a different type of container that is a real eye-opener. I'll take a look at Canonical's Snapcraft [4] package manager (also called the Snappy package manager), which is a concerted effort to mature and nurture a different way of placing applications in containers.

What, Where, Why

Ubuntu bravely states that snaps can "package any app for every Linux desktop, server, cloud or device, and deliver updates directly." As the product has matured, it's been used in Internet of Things (IoT) technologies and gained some momentum. The premise is that mobile devices, refrigerators, and parking meters should all be able to use portable containers packaged in a way that makes them device agnostic. In other words, you get to run lightweight applications without the fear that missing dependencies will break them. Intrigued? I think you should be.

If you're still not convinced, you should consider this note from the snaps GitHub [5] page before you get started:

Snaps are faster to install, easier to create, safer to run, and they update automatically and transactionally so your app is always fresh and never broken. You can bring your own build infrastructure or use ours.

When you install a snap for the first time, another small snap is also pulled down (at the time of writing, it's around 85MB), known as the core or ubuntu-core snap. It offers other snaps a read-only filesystem that can also rely on basic libraries, an init package (systemd), and networking. This is called an OS snap, because it is a minimal operating system of sorts. The clever part is that this OS snap allows you to run your snaps on any Linux distribution without fear of compatibility issues. Note that these OS snaps won't include the Apt package manager. I'll explain how to develop within an environment like this later.

The high-level view of a snap is that they're actually a SquashFS filesystem filled up with your application. To configure a snap, you mainly focus on a single pre-build config file called snapcraft.yaml, which Ubuntu has described as a fancy ZIP file brimming with all the dependencies you'll ever need for your app. You'd be correct in noticing that YAML-formatted files are taking over the planet.

What are the fundamental differences between a Docker image and a snap, for example? A snap's raison d'être is usually to have a read-only view of the operating system for security reasons and piggyback its networking onto the host's network stack, whereas Docker containers instead focus on being more autonomous (e.g., with their own internal IP address).

Really, snaps are just a different type of container for your app, with the emphasis on being completely portable. The snap will generally be able to access other snaps because they're read-only, and it will have its own predefined, independent writeable filesystem. Systemd also comes into play, and your snap's services are handled by the init daemon in the same way as host services (i.e., stops and starts are handled for you).

One key thing to bear in mind is that, because the filesystems for these autonomous containers are locked away from the host's filesystem, it's important to use relative paths in your snap's configuration and not absolute paths. Otherwise, everything will break!

Strictly Speaking

From a security perspective, the read-only mode to keep successful attacks from persisting after a container stop/restart is very welcome. When running your snaps, you get to choose between three types of confinement: strict, devmode, and classic. The first, strict, is the default; it locks things down and lets you insist that your snap is restricted in only accessing its own install space. Figure 1 shows how this is controlled.

Controlling filesystem access of default strict running mode (source: snapcraft.io [6]).
Figure 1: Controlling filesystem access of default strict running mode (source: snapcraft.io [6]).

To dev or Not to dev

Developer mode (devmode) offers a much looser set of controls, in the sense that, rather than actions being blocked, warnings are sent to Syslog (usually into one of the /var/log/messages or /var/log/syslog files, depending on the distribution). Some detail in the debugging documentation [7] explains that you should always add the --devmode option explicitly when you install a snap for sandbox work – even if you've loosened your security confinement settings within your YAML configuration file (snapcraft.yaml).

It's a Classic

To run a snap in a no-holds-barred, throwing-caution-to-the-wind mode, you select --classic, wherein the snap treats the host resources as its own. In the Snap Store, where a full catalog of snaps can be downloaded, any snaps set to run in classic mode are scrutinized by a human for safety reasons. I'll look in-depth about how you can develop your apps in classic mode in a moment.

Automatic Updates

Automatic updates are a natty addition. In the background, the current version of your snaps are checked against the latest versions and then updated if required on a daily basis. You can also use the refresh option to make sure you have the latest versions.

Innards

The Snapcraft docs [8] explain some of the intricacies of which files do what inside a typical snap (not at the pre-build stage). Figure 2 shows the key files involved. It's important not to confuse the snap.yaml and snapcraft.yaml files. The latter is used before the snap is compiled (i.e., the pre-build stage I mentioned before). The main file I'll look at is the snapcraft.yaml file. To whet your appetite, Listing 1 is a snippet from such a file, with some pertinent comments.

Listing 1: A Sample snapcraft.yaml File

---
name: Echo a name
version: "1.0" # I'm a string not a number, so put me in quotes
apps:
    echo-chrisbinnie:
        command: bin/echo # Note I drop the prepended slash to make it work inside a snap
The key files inside a snap (source: The snap format [9]).
Figure 2: The key files inside a snap (source: The snap format [9]).

In this basic example, you can see that this snippet is just "echoing" a string. Note that I'm intentionally using the relative path and not the usual absolute path, /usr/bin/host.

Getting Your Hands Dirty

Now that I've touched on the basics, I'll create a snap that does something useful. All the major distributions support snapd, from Arch Linux, Fedora, Gentoo, and openSUSE to OpenWrt and OpenEmbedded/Yocto, not to mention the obvious Debian and Ubuntu distributions.

I'll focus on my Linux Mint Debian laptop for convenience. To add the snapd package, I use the oh-so-arduous command (which might not even be necessary on some of the newer Ubuntu versions; I've read it's built in from 16.04 onward):

$ sudo apt install snapd

Snaps are usually pulled from the Snap Store [10], which is comparable to the Docker Hub. To get going, it's a case of searching for your snap. Again, this process isn't too dissimilar to using the Docker Hub. You'll find a lot of pre-built snaps to work with.

Shopping

If you don't have an Ubuntu One account, simply sign up. You'll also be shown the way to add your own snaps to the Snap Store once you've logged in. The marketing blurb includes the promise of getting your snap published within minutes and being able to reach millions of devices through the Snap Store.

Next, you will want to log in to the Snap Store:

$ snap login chris@binnie.tld

You should see a Login successful message.

Finding Nemo

Figure 3 shows a search using the keyword host. This is a command that you'll be using frequently with snap if you're pulling in other people's snaps. Instead of search, as in Docker, the Snap Store uses find to look for useful snaps.

Searching for a snap with the word "host" in the Name or Summary.
Figure 3: Searching for a snap with the word "host" in the Name or Summary.

To install one of the newly found packages, just use the install command (Figure 4). Opting sensibly for the second package from the top, because it's from Canonical, I use the command:

Installing a snap made by Canonical.
Figure 4: Installing a snap made by Canonical.
$ snap install wifi-ap

In the console, you can see the snap output as it installs successfully.

Shopping Lists

Another command you'll need frequently is the list command, which shows your installed snaps. Figure 5 shows two locally installed snaps: core and wifi-ap, both created by Canonical.

Running a list command displays local snap information.
Figure 5: Running a list command displays local snap information.

A quick glance at what the docs have to say about a Hello World! snap can be found on the Usage page [11]. I remember when I first saw this, I was surprised to see the output without any prepended command on the host's command prompt (i.e., no snap command). I just typed hello after installing the snap:

$ hello
Hello, world!

The output denotes success.

Remember the wifi-ap snap? This command has a lot of juicy information, which you can see by entering the info command (Listing 2). The description section should be self-explanatory; just below that, a URL points you to the snap's source code [12]. Also note the options listed under the commands section, which lists the possible parameters you can send to the snap.

Listing 2: Getting wifi-ap Snap Information

root@chris:~# snap info wifi-ap
name:      wifi-ap
summary:   "WiFi Access Point based on hostapd"
publisher: canonical
contact:   snappy-canonical-storeaccount@canonical.com
description: |
  This snap is implementing a WiFi access point based on hostapd and allows
  easily to share a internet connection or just create a network others can
  easily connect to.
  Please find the source of this snap at:
    https://code.launchpad.net/~snappy-hwe-team/snappy-hwe-snaps/+git/wifi-ap
commands:
  - wifi-ap.config
  - wifi-ap.setup-wizard
  - wifi-ap.status
tracking:    stable
installed:   15 (146) 31MB -
refreshed:   2017-05-05 16:20:19 +0000 UTC
channels:
  stable:    15     (146) 31MB -
  candidate: 15     (146) 31MB -
  beta:      16     (186) 31MB -
  edge:      17-dev (194) 31MB -

If you follow the source link to the Ubuntu Launchpad site, navigate to the Get this repository section, and then run the git clone command with the -b (branch) switch (Listing 3), you can download the latest source. If you then enter the wifi-ap directory, you can see, among all the small files, the all-important snapcraft.yaml file.

Listing 3: Getting the Latest wifi-ap Source Code

root@chris:~# git clone -b master https://git.launchpad.net/~snappy-hwe-team/snappy-hwe-snaps/+git/wifi-ap
Cloning into 'wifi-ap'...
remote: Counting objects: 2220, done.
remote: Compressing objects: 100% (1903/1903), done.
remote: Total 2220 (delta 882), reused 955 (delta 182)
Receiving objects: 100% (2220/2220), 2.71 MiB | 0 bytes/s, done.
Resolving deltas: 100% (882/882), done.
Checking connectivity... done.

Snap, Crackle, and Pop

Now I'll look at how you can create your own very basic snap. In this example, you just want to perform a super-simple echo command of whatever input (STDIN) is typed. To develop your own snaps, you need to install the snapcraft package:

$ apt install snapcraft

After a little more than 8MB of files are installed, you can start creating a snap with an init command (Figure 6), which pulls in a template.

Because I've already run hello and wifi-ap, those snaps are in the snap/ directory, along with the snapcraft.yaml file:

root@chris:~# ls snap/
core  hello  snapcraft.yaml  wifi-ap

Listing 4 shows the intuitive and easy-to-follow template file.

Listing 4: A snapcraft.yaml Template File

name: my-snap-name # you probably want to 'snapcraft register <name>'
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

Classic Chroots

With Ubuntu 16.04 onward, many of the snap tools required for building a snap appear to be readily available.

To avoid read-only filesystem issues (i.e., no useful package manager like Apt), you can create a chroot environment in which you develop your application. A chroot ignores the file path and some of the host system innards. Within the chrooted environment, you share the home/ directory between the container (your snap) and the ubuntu-core snap (your snap's OS). The next command lets you run a "classic" Ubuntu environment on a Snappy system:

$ snap install classic --edge --devmode
classic (edge) 16.04 from 'canonical' installed

When you run classic, it changes the prompt, after loads of output, by prepending (classic), which is also a reminder to always run this from your home directory so snapd knows what's going on.

$ classic
(classic)root@chris:~#

Unlike my example, you should probably avoid using root and, if needed, sudo from your user or use it directly.

You need to throw in some compilers and other build tools now, which, if your prompt has the classic prefix, gets chucked into your chroot and not directly onto your system:

(classic)root@chris:~# apt install build-essential

If you did not install the snapcraft package before this point, you should add that package to this command, as well. After hitting the Enter key, you're faced with the usual (potentially insecure, if you leave it on a system unnecessarily) more than 200MB of compiler tools, such as the omnipotent gcc package.

Make It Snappy

Unlike the init command example in Figure 6, in this case, I'm running it inside my chroot. For this example, I create and change to a directory called echo-chrisbinnie before running the init command. Pay close attention: If you're not in a directory off your home directory, your chroot might not work as expected and could potentially reference other system paths within the snap by accident.

Some helpful output from the friendly, neighborhood init function.
Figure 6: Some helpful output from the friendly, neighborhood init function.

In a slightly alarming oh-no-it's-all-badly-broken sort of way, you might see a screen full of errors. Fret not, though, because on Ubuntu 16.04, I entered two suggested export commands,

$ export LC_ALL=C.UTF-8; export LANG=C.UTF-8

and then ran the snapcraft init command again with complete success. Thankfully, it's happy days again when you see the friendly Edit the file to your liking greeting that you saw in Figure 6. As you've probably guessed, the next thing to do is edit the template file under the snap/ directory.

When I first looked at snaps, I have to admit that I needed to tinker with paths, and source file downloads, but ultimately, unless you're building a massive file from source, it's a relatively simple and rewarding experience to build your first snap. Once your YAML is correct, you witness a welcome screen of progress as your snap is being packaged (Figure 7). To compile your snapcraft.yaml file, just make sure you're in the snap/ directory within your chroot, and run the

(classic)root@chris:~/echo-chrisbinnie/snap# snapcraft

command a few times, adjusting your configuration according to any errors. Also, pay attention to the configflags compile flags (Listing 5, last line). Would you believe even a simple echo command needs tweaking with compile flags?!

Listing 5: Working Config for Echo Snap

name: echo-chrisbinnie
version: '0.1'
summary: Echo a name
description: |
  Enter a name and I will echo it.
grade: devel
confinement: devmode
apps:
  echo-chrisbinnie:
    command: bin/echo
  bash:
    command: bash
parts:
  echo-chrisbinnie:
    source: https://launchpad.net/ubuntu/+archive/primary/+files/coreutils_8.25.orig.tar.xz
    plugin: autotools
    configflags: ["FORCE_UNSAFE_CONFIGURE=1"]

If you get stuck, you'll find more docs [13] at the Ubuntu developer site that are much easier to use if you spin up a fully supported (ephemeral) Ubuntu 16.04 cloud instance/droplet/VM somewhere. Note that I simply looked up Ubuntu's 16.04 source files online for the coreutils URL (which is the package that provides the echo binary).

Once compilation and packaging was complete, I found an echo-chrisbinnie_0.1_amd64.snap file inside my snap/ directory. If you're happy that your version worked like mine (Figure 7), you can change confinement from devmode to strict and grade from devel to stable for publishing.

After loads of compiling and a bit of tweaking and rerunning, here's the snapcraft command in a chrooted environment; this output indicates everything is working as hoped (probably!).
Figure 7: After loads of compiling and a bit of tweaking and rerunning, here's the snapcraft command in a chrooted environment; this output indicates everything is working as hoped (probably!).

Now, you need to run the packaging command one more time:

$ snapcraft

Assuming your last line says Snapped with the snap name (and you're not uploading the snap to the Snap Store but installing it locally), you'll face the moment of truth when you try to execute your snap.

Oh No, It's Snapped!

Because your hand-crafted snap hasn't been signed by Canonical's Snap Store at this stage, you'll have to add --dangerous to the end of the install command. (By this stage, to publish your snap successfully, you have to have edited the config file with the changes to confinement and grade, as mentioned in the previous section). Even though confinement is strict, your snap is still apparently dangerous. Don't forget to run the snapcraft command whenever you make a change to your config file.

A simple snap installation looks like this example:

(classic)root@chris: ~/echo-chrisbinnie/snap# snap install echo-chrisbinnie_0.1_amd64.snap --dangerous
echo-chrisbinnie 0.1 installed

It's a Snap

Once you're happy that your snap is working, you should alter the name in your config file so that it will be appropriate and unique in the Snap Store. The next step is to upload to the Snap Store. If you've used Docker, the purpose of the following commands are easy to guess:

$ snapcraft login
$ snapcraft register echo-chrisbinnie
$ snapcraft push echo-chrisbinnie_0.1_amd64.snap --release=candidate

You need an Ubuntu One login for the first step; the second step reserves a name(space) for your snap. The --release option in the last line indicates that you are uploading to the candidate channel.

You should receive email saying that a number of unassisted robots are currently pouring over your code to make sure it's not going to combust spontaneously if someone tries to use it. After it's given a titanium thumbs-up, it will be available in the Snap Store.

The docs remind you that you can't push snaps with confinement set to devmode to either the stable or candidate channels. Figure 8 shows a successful upload; a few moments later, an Upload Status: Approved email should hit your inbox.

Publishing a snap to the Snap Store.
Figure 8: Publishing a snap to the Snap Store.

Keep It Snappy

To install this snap from the Snap Store after it's been automagically cleared for usage, it's simply a matter of entering the command:

$ snap install echo-chrisbinnie --channel=candidate

Outside of my chroot, directly on my host prompt, I can now see how my command works:

$ echo-chrisbinnie Chris Loves Linux
Chris Loves Linux

The End

According to the docs, "when you have a working snap, the next step is to use build.snapcraft.io to link your code repository and automate amd64 and armhf builds and releases." This apparently allows fully functional compatibility across many devices.

I hope to get some free time to experiment further with Ubuntu's snaps. Certainly, after a relatively cursory inspection, they hold lots of promise. Although I feel a little late to the party, I'm pleased with the nice, warm feeling knowing that the product has matured somewhat.

The security aspects in snaps make lots of sense for IoT devices and the like, considering that strict confinement is enforced by default. Over time, I'm sure the process of creating snaps will become second nature.

Although the container landscape shifts with changes in power and popularity, you should not dismiss the snaps concept. Should you want to delve deeper into this area, you might also want to look at a very similar product called Flatpak [14]. I hope this article has provided enough food for thought that you will give at least one of these products a try.