Create your own Ubuntu Core image

1. Overview

Building a bespoke image for a supported platform enables an Ubuntu Core device to be customised at the point of deployment. Customisation options include configuration for both hardware and software, specific kernels, and which snap packages to pre-install.

We are going to create an image, first by generating our own authority keys, then making the snap store aware of them, then creating and signing a model assertion before building the image.

This document will walk you through all the steps to build an image for an x86 device, but the same instructions will work for other platforms.

What you’ll learn

  • Different fundamental snap notions on the board, like gadget, kernel and core snap
  • Assembling a kernel and gadget snaps
  • Creating and using your authority keys
  • Create a model assertion for your target device
  • Compose and build a custom image using the ubuntu-image command

What you’ll need

  • Ubuntu 18.04 LTS desktop. You can’t do this on an Ubuntu Core device directly as creating your image can take quite some disk spaces. A VM can work as well.
  • A Snap Store account to register your authority keys
  • Some very basic knowledge of command line use, know how to edit files.

How will you use this tutorial?

What is your current level of experience?


2. Getting started

To build a custom image, first use the snapcraft command to login to the Snap Store:

$ snapcraft login

Snapcraft can be installed with sudo snap install snapcraft --classic, see Snapcraft overview for further details, and visit Create a developer account if you don’t yet have an account.

Before creating a custom model assertion, you will need to retrieve your developer ID and generate a properly formatted timestamp. The snapcraft command can be used to retrieve your developer id:

$ snapcraft whoami
email: <email>
developer-id: bJzr2XzZ56Qv6Z51HIeziXvxtn1XItIq

Use the following date command to output the correctly formatted timestamp for the model assertion:

$ date -Iseconds --utc
2020-03-10T12:34:57+00:00

3. Custom model assertion

The following is a tweaked JSON-formatted custom model assertion based on
ubuntu-core-18-amd64:

{
  "type": "model",
  "series": "16",
  "model": "ubuntu-core-18-amd64",
  "display-name":"Ubuntu Core 18 (amd64)",
  "architecture": "amd64",
  "kernel": "pc-kernel=18",
  "gadget": "pc=18",
  "base": "core18",
  "required-snaps": ["hello", "hello-world"],
  "authority-id": "bJzr2XzZ56Qv6Z51HIeziXvxtn1XItIq",
  "brand-id": "bJzr2XzZ56Qv6Z51HIeziXvxtn1XItIq",
  "timestamp": "2020-03-10T12:05:38+00:00"
}

We’ve saved the above example in a file called my-model.json , and it contains the following modified properties:

  • base: provides the run-time environment
    core18 is the current standard base and as is built from Ubuntu 18.04 LTS. See Base snaps for more details.
  • authority-id, brand-id: defines the authority signing the assertion
    reference assertions are signed by canonical. Non-reference assertions are
    signed by their brand store. For a custom model
    assertion, this needs to be the developer ID.
  • timestamp: UTC formatted time and date
    used to denote the assertion’s creation time.
  • required-snaps: one or more snaps to be pre-installed for deployment
    any snap can be listed here. If there’s a dependency on a different base, such as core, this will be installed too.

For a complete list of model assertion keywords, see Model assertion.


4. Signing a model assertion

The difference between building an image from a reference model assertion and
building from a modified model assertion is that the modified model assertion needs to be digitally signed. This is accomplished in four stages:

  1. create a key
  2. export/register the key
  3. sign the model assertion
  4. build the image

First, sign in to the Snap Store (snap login) and check whether there is already a published key available. You can list any published snaps with the snap keys command:

$ snap login
[...]
Login successful

$ snap keys
No keys registered, see `snapcraft create-key`

If you have no registered keys, create one as follows:

$ snap create-key my-key-name
Passphrase:
Confirm passphrase: <passphrase>

$ snap keys
Name         SHA3-384
my-key-name  E-n0AOKPFjIyy4S_i9JxTT4tkuaZf7rP9D2ARCmBNXjlgTGDjL8euFSlb87U0NPl

With a key created, use the snapcraft command to upload and register it with the store:

$ snapcraft register-key
[...]
Registering key...

A custom model assertion is signed by piping the assertion through the snap sign command with the key name as its sole argument:

$ cat my-model.json | snap sign -k my-key-name > my-model.model

The resulting my-model.model file contains the signed model assertion and can now be used to build the image.


5. Building the image

With a signed model assertion, the Ubuntu Core image can now be built just like a reference image, using the ubuntu-image command. First, install ubuntu-image if it isn’t already installed:

$ sudo snap install ubuntu-image --classic

You can now use ubuntu-image to build the image:

$ ubuntu-image snap my-model.model
Fetching snapd
Fetching pc-kernel
Fetching core18
Fetching pc
Fetching hello
Fetching hello-world
Fetching core
WARNING: model has base "core18" but some snaps ("hello", "hello-world") require "core" as base as well, for compatibility it was added implicitly, adding "core" explicitly is recommended

The output includes the img file itself, alongside seed.manifest and snaps.manifest files. These manifest files simply list the specific revision numbers for the snapd, pc, pc-kernel and core snaps built within the image.


6. Testing the image

You can now test the resulting image. Using QEMU, for instance, the following command will boot the image inside a VM and forward (SSH) port 22 to 8022 on your machine:

$ qemu-system-x86_64 -enable-kvm -smp 2 -m 1500 -netdev user,id=mynet0,hostfwd=tcp::8022-:22,hostfwd=tcp::8090-:80 \ 
-device virtio-net-pci,netdev=mynet0 -drive file=pc.img,format=raw

After running through the Ubuntu Core network setup and entering your account details, you will be able to SSH to your new Ubuntu Core deployment:

$ ssh <username>@localhost -p 8022

You are now connected to the Ubuntu Core virtual machine, from where you can configure and install whatever apps you need.

From within a running session on a custom image, you can run the pre-installed
snaps:

$ hello-world
Hello World!

Use snap list to see which snaps are installed:

$ snap list
Name         Version       Rev   Tracking  Publisher   Notes
core         16-2.43.3     8689  stable    canonical✓  core
core18       20200124      1668  stable    canonical✓  base
hello        2.10          38    stable    canonical✓  -
hello-world  6.4           29    stable    canonical✓  -
pc           18-2          36    18        canonical✓  gadget
pc-kernel    4.15.0-88.88  399   18        canonical✓  kernel
snapd        2.43.3        6434  stable    canonical✓  snapd

The snap known model command will show the read-only custom model assertion used to build the image:

$ snap known model
type: model
authority-id: bJzr2XzZ56Qv6Z51HIeziXvxtn1XItIq
series: 16
brand-id: bJzr2XzZ56Qv6Z51HIeziXvxtn1XItIq 
model: ubuntu-core-18-amd64
architecture: amd64
base: core18
display-name: Ubuntu Core 18 (amd64)
gadget: pc=18
kernel: pc-kernel=18
required-snaps:
  - hello
  - hello-world
timestamp: 2020-03-10T12:05:38+00:00
sign-key-sha3-384: 9aZR3b1UX9kqiVVxzfUrKYzYjHX-gC8jGNc4hTCpGfpPyaFdWR7K68HLoY1EH3yR
[...]

7. Congratulations!

You now have your own device image file for your specific device. This image is easily flashable on any SDCard or eMMC and can be booted right away.

You should by now be familiar with the various snaps composing an Ubuntu image: Core snap, kernel, gadget. You know that snapd is using a model assertion to define all pieces composing an image and this is what is used to build the image via the ubuntu-image tool.

Finally, you know also that you can change those default snaps, and add more applications snaps as you require them. If you produce your own gadget or kernel snap, you can swap as well default ones and enable a new board that way.

Next steps