Compact and Bijou
by Alan Pope on 21 January 2021
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.
Snapcraft behaviour is controlled by the
snapcraft.yaml file. One of the outputs of the snap build process is the is
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.
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.
echo “Hello world!”
The meta/snap.yaml looks like this.
summary: A very small shell script
This shell script is about as simple as they get.
But it could do a lot more.
Here’s what that directory structure looks like on the disk.
$ tree .
│ └── tinysnap.sh
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
Assembling the snap is very straightforward and fast.
$ snap pack .
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
tinysnap, so we can just run that.
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
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 .
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.