Package Python in a Snap

Getting newer versions of Python on long term support releases of Linux distributions, such as Ubuntu or CentOS, without interfering with the system Python can be pretty involved. Doing it on multiple instances is even harder. Fortunately, it is possible and quite easy by using snapd.

Snaps are applications running in a container, similar to Docker. Unlike Docker, which expects files or directories from your host system to be mounted in the container to bridge the gap between the container and your host, snaps work directly on your host system. Snaps can be tightly confined or they can be in classic mode which provides fewer restrictions and is better suited to packaging something like Python.

The driving principal behind this approach to packaging and distrbuting Python is to provide an easy way to install different Python versions in parallel on Linux distros that support snapd. These so-called snaps can be fine-grained to the point that one could conceivably install Python 3.6.0, 3.6.1, 3.6.2, and so on, all independent of each other. That's an extreme case but showcases the power of snaps. Another advantage is that the same snap can be installed on different distros without having to worry about slight differences between distros.

There are two aspects of working with snaps: building and distrbuting them. This post deals mostly with building a snap which provides Python 3.7 (pre-release). It can then be distributed through the snap store.

I wanted to be able to build snaps on non-Ubuntu machines, such as on macOS. Thanks to the great work of the Ubuntu engineering team along with the community and power of Docker, it is not only possible but fairly easy.

I have built a Docker image that packages Snapcraft in it. This image can be used to build any snap. This Docker image is available from Docker Hub (codeghar/snapcraft) with a simple docker pull codeghar/snapcraft.

Run a container with the following docker run command, edited to suit your needs, of course. This command was inspired by snap-docker.

docker run \
    --name=snappy \
    -d \
    -it \
    --tmpfs /run \
    --tmpfs /run/lock \
    --tmpfs /tmp \
    --cap-add SYS_ADMIN \
    --device=/dev/fuse \
    --security-opt apparmor:unconfined \
    --security-opt seccomp:unconfined \
    -v /sys/fs/cgroup:/sys/fs/cgroup:ro \
    -v /lib/modules:/lib/modules:ro \
    codeghar/snapcraft:latest

Once the container is running, create the directory structure as described in the Snapcraft docs, and create a snapcraft.yaml file. Then let snapcraft snap do its job. I have created a git repo, snap-python37, to demonstrate how easy it is. Why did I choose Python 3.7, since it only has alpha releases so far? To demonstrate how snaps can make it easy to consume any release of Python on any Linux distro that supports snaps. The snapcraft.yaml in this repo is simple and meant for development only. You can edit it to fit your needs and use it as you see fit (under the terms of the MIT license).

I have reserved the name python37 in the snap store. I'll try to release it to the stable channel when Python 3.7.0 is released. Meanwhile, you can build the snap yourself and install the .snap file locally.