Create your own Ubuntu Core 20 image

Key Value
Summary Create your own image of Ubuntu Core 20, the latest release of our snap-based operating system. Make your own device image with some snap preinstalled or additional functionalities!
Categories iot
Difficulty 3
Author Canonical Web Team webteam@canonical.com

Overview

Duration: 1:00

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 of the latest Ubuntu Core release, UC20, 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 20.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. See Create a developer account for details.
  • An SSH public key associated with your Snap Store account. See Adding SSH keys to your account.
  • Some very basic knowledge of command line use, know how to edit files.

How will you use this tutorial?

  • Only read through it
  • Read it and complete the exercises

0 voters

What is your current level of experience?

  • Novice
  • Intermediate
  • Proficient

0 voters

Getting started

Duration: 1:00

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. Make sure you have an SSH public key associated with your 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

You will need to use the following date command to output the correctly formatted timestamp for the model assertion, but not yet! The date must be more recent than any key you need to generate in a later step.

$ date -Iseconds --utc
2021-01-25T10:40:41+00:00

Custom model assertion

Duration: 4:00

The model assertion is a text-based document that contains the fundamental definition of a snap-based device. It describes what the system image includes and is signed by the brand account owning the device definition.

The model assertion contains:

  • Identification information, such as brand account id and model name.
  • Which essential snaps make up the device system, including the gadget snap, kernel snap and the boot base snap with the root filesystem.
  • Other required or optional snaps that implement the device functionality.
  • Additional options for the defined device, such as grade for a UC 20 device.

The model assertion is central to both the creation of the device image and the deployed device’s lifecycle, in particular it:

  • drives image creation via ubuntu-image. The resultant system seed includes the set of snaps and assertions specified in the model assertion.
  • allows first boot verification of the system seed before its snaps are turned into an installed system.
  • conveys model information, through registration from either the factory or in the field, which is used for cross-checking and registration affecting options, such as which account can issue a serial assertion for the device.

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

{
    "type": "model",
    "series": "16",
    "authority-id": "bJzr2XzZ56Qv6Z51HIeziXvxtn1XItIq",
    "brand-id": "bJzr2XzZ56Qv6Z51HIeziXvxtn1XItIq",
    "model": "ubuntu-core-20-amd64",
    "architecture": "amd64",
    "timestamp": "2021-01-25T10:40:41+00:00",
    "base": "core20",
    "grade": "signed",
    "snaps": [
        {
            "name": "pc",
            "type": "gadget",
            "default-channel": "20/stable",
            "id": "UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH"
        },
        {
            "name": "pc-kernel",
            "type": "kernel",
            "default-channel": "20/stable",
            "id": "pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza"
        },
        {
            "name": "core20",
            "type": "base",
            "default-channel": "latest/stable",
            "id": "DLqre5XGLbDqg9jPtiAhRRjDuPVa5X1q"
        },
         {
            "name": "snapd",
            "type": "snapd",
            "default-channel": "latest/stable",
            "id": "PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4"
        },
        {
            "name": "htop",
            "type": "app",
            "default-channel": "latest/stable",
            "id": "hJmReLmgXSUj4SF7WhyTVRV6IzUa4QUZ"
        }
    ]
}

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

  • base: provides the run-time environment. core20 is the newest base, built from Ubuntu 20.04 LTS. core18 is the current standard base and 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.
  • snaps: “pc”, “pc-kernel”, “core20” and “snapd” are all required for a functioning core20 device. In addition to those, the example shows the arbitrary addition of a snap called ‘htop’ from the Snap Store. This is purely for illustration purposes. The id field is from the output of snap info <snap-name> | grep snap-id to ensure the correct snap is references.

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

Signing a model assertion

Duration: 5:00

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...

You can safely generate the date string, as described in the Getting started section, and insert it into the model definition (the key we just created needs to be older than any generated date).

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.

Building the image

Duration: 3:00

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
ubuntu-image snap my-model.model
Fetching snapd
Fetching pc-kernel
Fetching core20
Fetching pc
Fetching htop

The output includes the img file itself, alongside _seed.manifest. The manifest file simply list the specific revision numbers for the snapd, htop, pc, pc-kernel and core20 snaps built into the image.

Testing the image

Duration: 8:00

To test your UC20 image with QEMU (https://www.qemu.org/), first install the OVMF package (eg. sudo apt install ovmf ) then run the following command to boot a UC20 image (renamed pc.img) within a virtual machine:

$ sudo qemu-system-x86_64 -smp 2 -m 2048 \
 -net nic,model=virtio -net user,hostfwd=tcp::8022-:22 \
 -drive file=/usr/share/OVMF/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on \
 -drive file=pc.img,cache=none,format=raw,id=disk1,if=none \
 -device virtio-blk-pci,drive=disk1,bootindex=1 -machine accel=kvm

Older OVMF packages can cause QEMU boot problems. Either build them manually, or consider extracting the files from a more recent package. OVMF for Ubuntu 20.04 LTS (Focal Fossa)](https://releases.ubuntu.com/20.04/) is known to work.

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, such as htop:

$ htop
[...]

Use snap list to see which snaps are installed:

snap list
Name       Version        Rev    Tracking       Publisher   Notes
core20     20201210       904    latest/stable  canonical✓  base
htop       3.0.5          2069   latest/stable  maxiberta   -
pc         20-0.4         115    20/stable      canonical✓  gadget
pc-kernel  5.4.0-64.72.1  708    20/stable      canonical✓  kernel
snapd      2.48.2         10707  latest/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: bJzr2XzZg6Qv6Z53HIeziXyxtn1XItIq
series: 16
brand-id: bJzr2XzZg6Qv6Z53HIeziXyxtn1XItIq
model: ubuntu-core-20-amd64
architecture: amd64
base: core20
grade: signed
snaps:
  -
    default-channel: 20/stable
    id: UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH
    name: pc
    type: gadget
  -
    default-channel: 20/stable
    id: pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza
    name: pc-kernel
    type: kernel
  -
    default-channel: latest/stable
    id: DLqre5XGLbDqg9jPtiAhRRjDuPVa5X1q
    name: core20
    type: base
  -
    default-channel: latest/stable
    id: PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4
    name: snapd
    type: snapd
  -
    default-channel: latest/stable
    id: hJmReLmgXSUj4SF7WhyTVRV6IzUa4QUZ
    name: htop
    type: app
timestamp: 2021-01-25T10:40:41+00:00
sign-key-sha3-384: 9aZR3b1UX9kqiVVxzfUrK
[...]

Congratulations!

Duration: 1:00

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

my-model.json has an extra comma on line 9 & causes the proceeding steps to fail.

parse error: Expected value before ',' at line 9, column 22

perhaps worth changing to:

{
    "type": "model",
    "series": "16",
    "authority-id": "bJzr2XzZ56Qv6Z51HIeziXvxtn1XItIq",
    "brand-id": "bJzr2XzZ56Qv6Z51HIeziXvxtn1XItIq",
    "model": "ubuntu-core-20-amd64",
    "architecture": "amd64",
    "timestamp": "2021-01-25T10:40:41+00:00",
    "base": "core20",
    "grade": "signed",
    "snaps": [
        {
            "name": "pc",
            "type": "gadget",
            "default-channel": "20/stable",
            "id": "UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH"
        },
        {
            "name": "pc-kernel",
            "type": "kernel",
            "default-channel": "20/stable",
            "id": "pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza"
        },
        {
            "name": "core20",
            "type": "base",
            "default-channel": "latest/stable",
            "id": "DLqre5XGLbDqg9jPtiAhRRjDuPVa5X1q"
        },
         {
            "name": "snapd",
            "type": "snapd",
            "default-channel": "latest/stable",
            "id": "PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4"
        },
        {
            "name": "htop",
            "type": "app",
            "default-channel": "latest/stable",
            "id": "hJmReLmgXSUj4SF7WhyTVRV6IzUa4QUZ"
        }
    ]
}

can test the change with

cat my-model.json | jq '.'

Thank you for letting us know, and for making it easy to update the doc - and sorry about the extra comma! I’ve just updated the tutorial to remove it.

Thank you for the good tutorial!

One more line, I found useful while gathering snap info to add the the model json

snap info jq | tr -d ' ' | jq -R '{ (split(":")[0]): split(":")[1] }' | jq -s add


{
  "name": "jq",
  "summary": "Thejqcommand",
  "publisher": "Canonical*",
  "store-url": "https//snapcraft.io/jq",
  "contact": "snaps@canonical.com",
  "license": "OtherOpenSource",
  "description": "|",
  "Thejqcommand": null,
  "snap-id": "8X4ytHZ2xX4kNkr8V2NU3AQuoMlglwED",
  "channels": "",
  "latest/stable": "1.5+dfsg-12017-05-17(6)245kB-",
  "latest/candidate": "1.5+dfsg-12017-05-17(6)245kB-",
  "latest/beta": "1.62018-11-19(11)1MB-",
  "latest/edge": "1.62020-06-15(13)1MB-"
}
1 Like

Great article well written. Some suggestions from someone who has just followed it step by step.

  1. I created the date time as specified. Put it in the model, then when I went to sign the key, I got an error that it was invalid. One should first generate the key, so its older than the data/time later generated. Following the article sets one up for this error.

  2. Was great to add the htop example. Maybe I missed whats obvious to others, but how does one add other software? Would be great if one added an explanation as to how one finds the name/type/default-channel/id for other packages. EDIT: I see thanks to @john-s response above, that snap info is the command one uses. Maybe this sentence:

The id field is from the output of snap info | grep snap-id to ensure the correct snap is references.

Maybe to something like: "To find the details of other snap packages you may wish to add, you can use the snap info <snap-name> command. The id field is from the output of snap info <snap-name> | grep snap-id to ensure the correct snap is references." …or maybe I am just below the bell curve in this matter and leave it as is.

  1. Minor detail: it created seed.manifest for me, the article has a leading underscore in the name.

  2. Under the section 6. Testing the image, sound like to me just verifying the image works, until I read from where you can configure and install whatever apps you need.. Does this mean I am configuring the image, or the virtual machine instance? Is this a way to customize the image further, or just test it?

  3. I could only get qemu to work by first installing: sudo apt install qemu-system

Well written, friendly, and engaging article, well done! :grinning:

It very followable until 6. Testing the image. As mentioned above one may need to run sudo apt install qemu-system. Then VM starts up. I get to a page :

Profile setup

Enter an email address from your account in the store. email address: ______________________

If I open a guest browser, and go to https://login.ubuntu.com I can sign in no problem with my email address. If I try put it in Profile setup it says:

Creating user failed: error: while creating user: cannot create user for “foo@gmail.com”: no ssh keys found

Do I need to create ssh keys at ubuntu.com or on this VM? Is it the key made from snap keys or do I generate a new pair? Do I create some arbitrary private/pub key pair combo and place the public key on https://login.ubuntu.com/ssh-keys? Not sure what to do. Maybe a sentence or two could be added to the article to make this point clear.

Having tried a few things, to anyone else, the solution is to generate your own pub/private key pair, and place the public key at: https://login.ubuntu.com/ssh-keys

Thanks! :+1:

Did you try snap login?

Yes I did thanks, I was just missing SSH keys being uploaded to ubuntu.com

1 Like

What is the "series : " part do? Documentation is vague and says “like 16 or 18”, but what does that reference to or do with a CORE20 build?

Sorry for the delay in answering your question.

As I understand it, the answer is actually that series: 16 is rather arbitrary, and the majority of assertions simply stick with series: 16. It does not correlate to an Ubuntu release, for example, but could be used to define how a definition or namespace might be compatible with other series, such as 18 being compatible with 18 and backwards compatible with 16.

I’ve updated our Glossary to hopefully explain this better, and I’ll link to this when mentioning series:: Glossary