Ubuntu Blog: Compact and Bijou

Snaps are designed to be self-contained packages of binaries, libraries and other assets. A snap might end up being quite bulky if the primary application it contains has many additional dependencies. This is a by-product of the snap needing to run on any Linux distribution where dependencies cannot always be expected to be installed.

This is offset by the snap being compressed on disk, and the Snap Store delivering delta updates rather than force a full download on each update. Furthermore the concept of “shared content” or “platform” snaps allows for common bundles of libraries to be installed only once and then reused across multiple snaps. 

Typically in documentation we detail building snaps with the command line tool snapcraft. Snapcraft has logic to pull in and stage any required dependencies. We generally recommend using snapcraft because it helps automate things, and make the snapping process more reliable.

But what if your application has minimal, or no dependencies?. Your program might be a single binary written in a modern language like go or rust. Maybe it’s a simple shell or python script, which requires no additional dependencies. Well, there’s a couple of other interesting ways to build a snap we should look at.

How meta

Snapcraft behaviour is controlled by the snapcraft.yaml file. One of the outputs of the snap build process is the is snap.yaml. The snap.yaml contains metadata about the contents of the snap, which is consumed by the Snap Store when published, and by snapd on clients at download and installation time.

In the final stages of a snapcraft run, all the assembled components are primed (that is, collated in a folder prior to compression), then compressed into a .snap file. The generated snap.yaml is bundled into the snap file in the /meta folder. It is required by the system which receives the snap.

The snap.yaml has some similarities to the snapcraft.yaml, but is usually generated by snapcraft, not manually crafted by hand. That doesn’t have to be the case though. It’s possible to bypass snapcraft completely and create a snap using only the snap.yaml, the snap command and the binaries, scripts, libraries and other assets, which need snapping.

Let’s take an example of snapping a very simple shell script using this method. The “script” (such that it is) is created as bin/tinysnap.sh, but it could equally be a pre-compiled static binary.

#!/bin/bash
echo “Hello world!”

The meta/snap.yaml looks like this.

name: tinysnap
version: 0
summary: A very small shell script
description: |
  This shell script is about as simple as they get.
  But it could do a lot more.
base: core18
apps:
  tinysnap:
   command: bin/tinysnap.sh

Here’s what that directory structure looks like on the disk.

$ tree .
.
├── bin
│   └── tinysnap.sh
└── meta
    └── snap.yaml

2 directories, 2 files

That’s it. There’s no bundled dependencies, just the shell script itself. We specify core18 as the base, which means the Ubuntu 18.04 LTS-based core18 snap will be required. This is likely to already be installed on a system which has any snaps installed. The core18 snap contains the /bin/bash binary, so no need for us to bundle that inside our tiny snap. We just ship the shell script itself and the metadata in snap.yaml.

Assembling the snap is very straightforward and fast.

$ snap pack .
built: tinysnap_0_all.snap

We have a small snap!

Small is beautiful

$ ls -l tinysnap_0_all.snap 
-rw-r--r-- 1 alan alan 4096 Jan 21 10:56 tinysnap_0_all.snap

4096 bytes is the smallest we can get it down to, even though the script itself is mere tens of bytes in length. Given 4KiB is the likely smallest allocation unit on disk, I’m not going to stress about the padding in the squashfs file taking it up to that size.

Installing the resulting tiny snap is just the same as any other locally installed package. Specify the --dangerous flag to indicate we accept the risk associated with installing local un-checked packages.

$ snap install tinysnap_0_all.snap --dangerous
tinysnap 0 installed

Running is simple, since we expose the binary to the outside of the snap with the apps stanza in the snap.yaml as tinysnap, so we can just run that.

$ tinysnap
Hello world

We have specified no plugs, so there will be no interfaces connected with this snap. It’s completely confined. If we wanted to make something which can reach the network, or monitor system usage, we could specify the plugs in the snap.yaml.

It’s worth noting the snapcraft command also has a pack option which achieves the same as snap pack, but with extra checks, and developer feedback.

$ snapcraft pack .
Snapping |
Snapped tinysnap_0_all.snap

I imagine there’s quite a bit of functionality it would be possible to fit in a shell script, which packs down to 4KiB. It would be interesting to see how much you can squeeze in that space. Anyone up for the challenge?

You can find us over on the snapcraft forums, if you have any questions, comments or want to show off your tiny marvels.

Photo by David Maltais on Unsplash

About the author

Leave a Reply

Your email address will not be published. Required fields are marked *