Build on Docker

Snapcraft, the snap-building tool, is designed to use Multipass and bases to both simplify the build process and to confine the build environment within a virtual machine. This mostly removes the need to use Docker.

However, Docker can still be used, and is particularly useful when you’re already using Docker within your build and test infrastructure. All you need is a working snapcraft.yaml (see Creating a snap for more details).

To create a snap with Docker, first make sure you have Docker installed. You can test that Docker is correctly set up with:

$ docker run hello-world
[...]
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
[...]

If you don’t see the “Hello from Docker!” message, consult the Docker documentation for troubleshooting steps.

Snaps using bases

When using a base snaps other than core, a custom Docker image will need to be built. For details, see Creating docker images for snapcraft.

The process for building a core18 compatible image, the default for Snapcraft 3.x, is outlined below.

Docker images are built from a Dockerfile. This is a script that Docker interprets to assemble whatever is required to generate the image.

The following is an example Dockerfile to build a core18 compatible image:

stable.Dockerfile for core18
FROM ubuntu:bionic as builder

# Grab dependencies
RUN apt update
RUN apt dist-upgrade --yes
RUN apt install --yes \
      curl \
      sudo \
      jq \
      squashfs-tools

# Grab the core snap from the stable channel and unpack it in the proper place
# (the 'Snap-CDN: none' header allows building in restricted network
# environments such as Launchpad builders)
RUN curl -L -H 'Snap-CDN: none' $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/core' | jq '.download_url' -r) --output core.snap
RUN mkdir -p /snap/core
RUN unsquashfs -d /snap/core/current core.snap

# Grab the core18 snap from the stable channel and unpack it in the proper place
RUN curl -L -H 'Snap-CDN: none' $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/core18' | jq '.download_url' -r) --output core18.snap
RUN mkdir -p /snap/core18
RUN unsquashfs -d /snap/core18/current core18.snap

# Grab the snapcraft snap from the stable channel and unpack it in the proper place
RUN curl -L -H 'Snap-CDN: none' $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/snapcraft?channel=stable' | jq '.download_url' -r) --output snapcraft.snap
RUN mkdir -p /snap/snapcraft
RUN unsquashfs -d /snap/snapcraft/current snapcraft.snap

# Create a snapcraft runner
RUN mkdir -p /snap/bin
RUN echo "#!/bin/sh" > /snap/bin/snapcraft
RUN snap_version="$(awk '/^version:/{print $2}' /snap/snapcraft/current/meta/snap.yaml)" && echo "export SNAP_VERSION=\"$snap_version\"" >> /snap/bin/snapcraft
RUN echo 'exec "$SNAP/usr/bin/python3" "$SNAP/bin/snapcraft" "$@"' >> /snap/bin/snapcraft
RUN chmod +x /snap/bin/snapcraft

# Multi-stage build, only need the snaps from the builder. Copy them one at a
# time so they can be cached.
FROM ubuntu:bionic
COPY --from=builder /snap/core /snap/core
COPY --from=builder /snap/core18 /snap/core18
COPY --from=builder /snap/snapcraft /snap/snapcraft
COPY --from=builder /snap/bin/snapcraft /snap/bin/snapcraft

# Generate locale
RUN apt update && apt dist-upgrade --yes && apt install --yes sudo snapd locales && locale-gen en_US.UTF-8

# Set the proper environment
ENV LANG="en_US.UTF-8"
ENV LANGUAGE="en_US:en"
ENV LC_ALL="en_US.UTF-8"
ENV PATH="/snap/bin:$PATH"
ENV SNAP="/snap/snapcraft/current"
ENV SNAP_NAME="snapcraft"
ENV SNAP_ARCH="amd64"

Dockerfiles for the Snapcraft project, including files that can be built with snapd from different channels, can be found on Snapcraft’s GitHub repository.

To build a Docker image, enter the following command from the same location as the saved version of the Dockerfile, which we’ve called stable.Dockerfile:

$ docker build --no-cache -f stable.Dockerfile --label mycustomimage --tag mycustomimage:stable --network host .

When the process has completed, you should be able to see the new image in the output from docker images:

REPOSITORY      TAG       IMAGE ID       CREATED              SIZE
mycustomimage   stable    76dcf5eafcd2   About a minute ago   882MB

Snaps without bases

If your snapcraft.yaml has no base entry or base: core defined, you can simply pull down the latest snapcraft image:

$ docker pull snapcore/snapcraft:stable
[...]
Status: Downloaded newer image for snapcore/snapcraft:stable

Running a build

After either building or downloading the snapcraft Docker image, return to the root directory of the project containing your snapcraft.yaml and run snapcraft:

$ docker run -v "$PWD":/build -w /build <IMAGE-NAME> snapcraft

Repleace <IMAGE-NAME> with either the name of your manually built Docker image, mycustomimage:stable in our example above, or the downloaded image, such as snapcore/snapcraft:stable.

These options instruct Docker to map the current directory, your project root, to the /snapcraft_build directory inside the container, and then start the snapcraft command (the final command-line argument) from this same location inside the container.

When the snap build completes successfully, you will find a .snap file in the current directory. You can inspect its contents to ensure it contains all of your application’s assets:

$ unsquashfs -l *.snap

Next steps

After creating a snap, you should upload it to the Snap Store, from where it can reach a potential audience of millions. See Releasing your app for further details.

2 Likes

You can also use the snapcore/snapcraft image.

1 Like

not certain what that means.

Do you mean there is a docker snapcore/snapcraft image ?

Indeed: https://hub.docker.com/r/snapcore/snapcraft/

1 Like

This topic duplicates with Building the snap on Docker, maybe merge to one of them?

1 Like

I agree, I think this topic should be closed and point to the other one

Also, the other topic reads better on docs.snapcraft.io: https://docs.snapcraft.io/building-the-snap-on-docker/6757 reads better and is more consistent than https://docs.snapcraft.io/build-on-docker/4158, which also appears to suffer from some formatting issues converting from here

1 Like

Hello,

I think this doc should be superseded by https://docs.snapcraft.io/building-the-snap-on-docker/6757

First of all, it gives docker installation instruction which seems out of scope (and a moving target depending on version of ubuntu and docker).

Then it uses sudo to interact with docker, which is not needed if the user is in the docker group.

Finally, it forgets to mention the command to be run against the docker container, so:

$ sudo docker run -v "$PWD":/snapcraft_build \
       -w /snapcraft_build snapcore/snapcraft 
$ snapcraft

This doesn’t result in an interactive shell, so either it should be something like (-it was added to Keep STDIN open even if not attached and Allocate a pseudo-TTY; we could also add --rm to clean after the operation):

$ docker run -it -v "$PWD:/snapcraft_build" -w /snapcraft_build snapcore/snapcraft
# snapcraft       // <-- this is being executed within the container

Or something like the other page suggests (different folder names don’t matter, but snapcraft at the end does).

$ docker run -v "$PWD":/build -w /build snapcore/snapcraft snapcraft

Another things is that we need more than just snapcraft, see Building the snap on Docker - #2 by ijohnson

I think this should be updated to mention that before running snapcraft from the container, apt update needs to be run, because as it is the snapcore/snapcraft docker image has out of date apt sources and as such snapcraft will fail to install/download packages when using apt because the sources/sites are out of date

What do you folks think?

1 Like

Thanks for the input on this important topic. And yes, we should have only a single doc for Docker - and make sure it works - I’ll look into merging the two together, or reverting to the newer version, as suggested.

3 Likes

The docker command should probably read docker run --rm -v ... otherwise each run will leave a container behind.

1 Like

Great point - thanks! I’ll add it to the command.

The current format of a snapcraft.yaml allows values for “title” and “license”. But when using the “snapcore/snapcraft” Docker image, I get:

Issues while validating snapcraft.yaml: Additional properties are not allowed (‘title’, ‘license’ were unexpected)

Also, it’s confusing to have seemingly two official Docker Hub organizations, both with seemingly outdated images:

The first one looks to be “more official” because of the Ubuntu logo and link to the website, but the official snapcraft docs link to the second one.

@pans validation error is because default image, which is latest, contains outdated 2.43.1 version of snapcraft. I filled https://bugs.launchpad.net/snapcraft/+bug/1823519 to track this.

Is there a core18 version of Dockerfile?
Just changing from xenial to bionic does not cut it. It seems that line 12:

RUN curl -L $(curl -H ‘X-Ubuntu-Series: 16’ ‘https://api.snapcraft.io/api/v1/snaps/details/core’ | jq ‘.download_url’ -r) --output core.snap

should reference Series: 18 and have a different core downloaded.

Any thoughts?

Yeah you’d think so, but no. I hit that a while back, too :stuck_out_tongue: . Check this core18 Dockerfile out.

Thanks for the prompt reply! I got it working on my Mac with a few other changes. It’s slower than multipass to build, but at least I don’t have to shut down my Vbox instances to run it.

On https://snapcraft.io/docs/build-on-docker the example Dockerfile for building for core18 first references ubuntu:bionic, but then in the middle is based on ubuntu:xenial before the copy commands

The script postet by @kyrofa is more like I expected. This script additionally downloads the core18 snap, which the documentation does not do.

I propose the following changes to the documentation

FROM ubuntu:bionic as builder

# Grab dependencies
RUN apt-get update
RUN apt-get dist-upgrade --yes
RUN apt-get install --yes \
      curl \
      jq \
      squashfs-tools

# Grab the core snap from the stable channel and unpack it in the proper place
RUN curl -L $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/core' | jq '.download_url' -r) --output core.snap
RUN mkdir -p /snap/core
RUN unsquashfs -d /snap/core/current core.snap

+# Grab the core18 snap from the stable channel and unpack it in the proper place
+RUN curl -L $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/core18' | jq '.download_url' -r) --output core18.snap
+RUN mkdir -p /snap/core18
+RUN unsquashfs -d /snap/core18/current core18.snap
+
# Grab the snapcraft snap from the stable channel and unpack it in the proper place
RUN curl -L $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/snapcraft?channel=stable' | jq '.download_url' -r) --output snapcraft.snap
RUN mkdir -p /snap/snapcraft
RUN unsquashfs -d /snap/snapcraft/current snapcraft.snap

# Create a snapcraft runner (TODO: move version detection to the core of snapcraft)
RUN mkdir -p /snap/bin
RUN echo "#!/bin/sh" > /snap/bin/snapcraft
RUN snap_version="$(awk '/^version:/{print $2}' /snap/snapcraft/current/meta/snap.yaml)" && echo "export SNAP_VERSION=\"$snap_version\"" >> /snap/bin/snapcraft
RUN echo 'exec "$SNAP/usr/bin/python3" "$SNAP/bin/snapcraft" "$@"' >> /snap/bin/snapcraft
RUN chmod +x /snap/bin/snapcraft

# Multi-stage build, only need the snaps from the builder. Copy them one at a
# time so they can be cached.
-FROM ubuntu:xenial
+FROM ubuntu:bionic
COPY --from=builder /snap/core /snap/core
+COPY --from=builder /snap/core18 /snap/core18
COPY --from=builder /snap/snapcraft /snap/snapcraft
COPY --from=builder /snap/bin/snapcraft /snap/bin/snapcraft

# Generate locale
RUN apt-get update && apt-get dist-upgrade --yes && apt-get install --yes sudo locales && locale-gen en_US.UTF-8

# Set the proper environment
ENV LANG="en_US.UTF-8"
ENV LANGUAGE="en_US:en"
ENV LC_ALL="en_US.UTF-8"
ENV PATH="/snap/bin:$PATH"
ENV SNAP="/snap/snapcraft/current"
ENV SNAP_NAME="snapcraft"
ENV SNAP_ARCH="amd64"
1 Like

Thank you for proposing those changes! I’ve merged them into the documented script.

I also added snapd as a dependency because, while testing, the image build failed with A tool snapcraft depends on could not be found: ‘snap’. I believe snapcraft now requires snapd externally.

1 Like

After some internal discussion, I updated the sample Dockerfile to add Snap-CDN: none HTTP headers in order to work properly in the Launchpad build farm.

1 Like

Hi @degville & others,

Thanks for this helpful thread & info. I tried out the build in Docker and that absolutely works fine. However, the point raised by @kyrofa is still valid: There is a prebuilt Docker image on Dockerhub from the organisation “snapcraft”, which contains images that are hopelessly outdated: snapcraft’s Profile | Docker Hub

And there is a Dockerhub org “snapcore” which does seem to contain an up to date Docker image that matches the current Dockerfile from the snapcraft repo: snapcore’s Profile | Docker Hub

Is there a way we could improve this? Could we link from these docs directly to the correct Dockerhub? That would help save people wanting to use Docker some time.

Kind regards,

Brecht

FYI, an anecdote, but I’ve had to move away from docker entirely because snapcraft’s core plugins are making wider use of build-snaps, which don’t work on docker. Tread carefully.