Package Docker images in a snap

Docker containers can be deployed on Ubuntu Core from the command-line, or via a Docker companion snap.

This guide explains how Docker images can be bundled inside snaps to be deployed together with other resources within the package.

Downloading Docker images from an online registry during the installation is a common approach. There are however situations where this isn’t desired:

  • Air-gapped environment with no internet connection
  • Insecure Docker registry or untrusted image publisher
  • Local Docker image including proprietary software
  • Docker image built together with the snap

Consequently, bundling the Docker image inside a snap is useful for both existing and locally built images.

Snaps are signed, which means the entire package, including the Docker image, is a verifiable artefact that can be pulled from an online or on-prem store, built into Ubuntu Core images, or passed around as standalone snaps.

Building a snap that contains and uses an existing image involves a few steps.

Save the image to a file

This can be done before or during the snap’s build. We’ll do this before the build, assuming that a Docker Engine is available to the host, rather than adding one to the snap’s build environment.

To save our image to a file, we should first make sure it is loaded into the Docker Engine.

The image is loaded to the engine when built locally (e.g. docker build --tag myapp .) or pulled from a registry (e.g. docker pull <image>):

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
myapp     latest      93c368f790a8   2 minutes ago   125MB

Let’s save the image as a tar file:

$ docker save --output myapp.tar myapp

$ du -h myapp.tar 
123M    myapp.tar

Don’t worry about the size; this gets significantly smaller when compressed inside the snap. Moreover, upgrades to the snap will only deploy the delta, calculated on the compressed package, rather than the whole blob.

Add the image to the snap

Make sure that the snap has a part that sources the image.

For example, with a part that sources all the project files:

parts:
  all-files:
    plugin: dump
    source: .

The tar file would be made available inside the snap, on target at $SNAP/myapp.tar.

Load the image before creating the container

Add the following command to load the image to the Docker Engine before creating the container:

docker load --input $SNAP/myapp.tar

This could be in the service’s command or the install/refresh hooks.

That’s it. You should now be able to build the snap as usual.
Refer below for a complete example.

Example: RabbitMQ Docker companion snap

Let’s take the snap created in the Docker companion snap guide.

With that snap, the Docker image was pulled from DockerHub on first startup and after every refresh (upgrade/downgrade). This is because the image wasn’t available locally.

What we need to do is to make sure the image is packaged inside the snap and loaded into the Docker Engine before the container creation.

On the host, pull the image to load it into the Docker Engine:

docker pull rabbitmq:3.13-management

Create the snap/local/image directory:

mkdir snap/local/image

Save the image inside it:

docker save --output snap/local/image/rabbitmq.tar rabbitmq:3.13-management

The image directory gets added to the snap because of the existing local part defined in snapcraft.yaml.

In snap/local/bin/run.sh:

  • Add the following docker load command right before creating the container:
if [ ... ]; then
    ...
    
    docker load --input $SNAP/image/rabbitmq.tar

    docker create \
        ...
fi
2 Likes