Build and Publish Snap Packages using Snapcraft on CircleCI
Snap packages provide a quick way to publish your software on multiple Linux distributions (distros). This document shows you how to build a snap package and publish it to the Snap Store using CircleCI.
A .snap file can be created once and installed on any Linux distros that supports
snapd such as Ubuntu, Debian, Fedora, Arch, and more. More information on Snapcraft itself can be found on Snapcraft’s website.
Building a snap on CircleCI is mostly the same as your local machine, wrapped with CircleCI 2.0 syntax. This document describes how to build a snap package and publish it to the Snap Store via CircleCI. The following sections use snippets of a sample
.circleci/config.yml file with the full version at the end of this doc.
To build a snap in any environment (local, company servers CI, etc) there needs to be a Snapcraft config file. Typically this will be located at
snap/snapcraft.yml. This doc assumes you already have this file and can build snaps successfully on your local machine. If not, you can read through the Build Your First Snap doc by Snapcraft to get your snap building on your local machine.
... version: 2 jobs: build: docker: - image: cibuilds/snapcraft:stable ...
docker executor is used here with the
cibuilds/snapcraft Docker image. This image is based on the official
snapcore/snapcraft Docker image by Canonical with all of the command-line tools you’d want to be installed in a CI environment. This image includes the
snapcraft command which will be used to build the actual snap.
... steps: - checkout - run: name: "Build Snap" command: snapcraft ...
On CircleCI, this single command is needed to actually build your snap. This will run Snapcraft, which will then go through all of it’s build steps and generate a
.snap file for you. This file will typically be in the format of
Unit testing your code has been covered extensively in our blog and our docs and is out of the scope of this document. You’ll likely want to create a
job before building the snap that pulls project dependencies, any pre-checks you’d want to do, testing, and compiling.
Building snaps on CircleCI results in a
.snap file which is testable in addition to the code that created it. How you test the snap itself is up to you. Some users will attempt to install the snap in various distros and then run a command to make sure that installation process works. Snapcraft offers a build fleet for spreadtesting that allows you to test snaps on different distros, after you’ve already tested the code itself. This can be found here.
Publishing a snap is more or less a two-step process. Here’s on this might look on a Linux machine:
snapcraft login # follow prompts for logging in with an Ubuntu One account snapcraft export-login snapcraft.login base64 snapcraft.login | xsel --clipboard
- Create a Snapcraft “login file” on your local machine that we upload to CircleCI. Assuming your local machine already has these tools installed and you are logged in to the Snapcraft Store (
snapcraft login), we use the command
snapcraft export-login snapcraft.loginto generate a login file called
snapcraft.login. As we don’t want this file visible to the public or stored in the Git repository, we will base64 encode this file and store it in a private environment variable called
... - run: name: "Publish to Store" command: | mkdir .snapcraft echo $SNAPCRAFT_LOGIN_FILE | base64 --decode --ignore-garbage > .snapcraft/snapcraft.cfg snapcraft push *.snap --release stable ...
- Once the base64 encoded version of the file is stored on CircleCI as a private environment variable, we can then use it within a build to automatically publish to the store.
In this example, Snapcraft automatically looks for login credentials in
.snapcraft/snapcraft.cfg and the environment variable made previously is decoded into that location. The
snapcraft push command is then used to upload the .snap file into the Snap Store.
Uploading vs Releasing
snapcraft push *.snap by default will upload the snap to the Snap Store, run any store checks on the server side, and then stop. The snap won’t be “released” meaning users won’t automatically see the update. The snap can be published locally with the
snap release <release-id> command or by logging into the Snap Store and clicking the release button.
In typical CircleCI fashion, we can go fully automated (as in the above example) but using the
--release <channel> flag. This uploads the snap, does Store side verification, and then will automatically release the snap in the specified channels.
We can utilize multiple jobs to better organize our snap build. A job to build/compile the actual project, a job to build the snap itself, and a job that published the snap (and other packages) only on
master would all be useful.
Workflows can help with building snaps in two ways:
- Snap Store Channels - As we mentioned in the previous section, when we upload to the Store we could optionally release at the same time. This allows us to designate specific jobs on CircleCI to deploy to specific Snap Channels. For example, the
masterbranch could be used to deploy to the
while tagged releases could be used to deploy to thestable` channel.
- Parallelize Packing - If your software is being packaged as a snap as well as something else, say a flatpak, .deb, .apk, etc, each package type could be placed in its own job and all run parallel. This allows your build to complete must fast than if the .deb package could start to build until the snap completed, and so on.
workspaces to move a generated snap file between jobs when necessary. Here’s an example showing a snippet from the “from” job and a snippet of the “to” job:
... # from a job that already has the snap - persist_to_workspace: root: . paths: - "*.snap" ... # to the next job that needs the snap - attach_workspace: at: . ...
Below is a complete example of how a snap package could be built on CircleCI. This same process is used the build the Snap pakcage for the [CircleCI Local CLI][local-cli-repo].
Full Example Config
version: 2 jobs: build: docker: - image: cibuilds/snapcraft:stable steps: - checkout - run: name: "Build Snap" command: snapcraft - persist_to_workspace: root: . paths: - "*.snap" publish: docker: - image: cibuilds/snapcraft:stable steps: - attach_workspace: at: . - run: name: "Publish to Store" command: | mkdir .snapcraft echo $SNAPCRAFT_LOGIN_FILE | base64 --decode --ignore-garbage > .snapcraft/snapcraft.cfg snapcraft push *.snap --release stable workflows: version: 2 main: jobs: - build - publish: requires: - build filters: branches: only: master