How to create a Real-time Ubuntu Core image

Note

This guide assumes access to features currently available exclusively to dedicated Snap Store users.

Canonical builds and publishes generic and silicon-optimized Ubuntu Core images for a range of supported platforms. The generic Ubuntu Core images include the generic Ubuntu kernel by default. In order to run Ubuntu Core with the real-time kernel, we need to build it ourselves.

This guide shows how to build an Ubuntu Core image with the real-time kernel. We will first build a vanilla image - which is excellent for testing and tuning - then proceed to making a custom image with production-ready kernel configurations.

Create an image with the real-time kernel

To do this we need to describe the image content and then use a tool to build it.

Create the model assertion

The model assertion is a digitally signed document that describes the content of the Ubuntu Core image. Read the model assertion documentation before continuing.

Below are example model assertions, describing the Ubuntu Core image content for recent releases:

{
    "type": "model",
    "series": "16",
    "model": "ubuntu-core-24-amd64",
    "architecture": "amd64",
    "base": "core24",
    "grade": "dangerous",
    "authority-id": "<developer-id>",
    "brand-id": "<developer-id>",
    "timestamp": "<timestamp>",
    "store": "<brand-store-id>",
    "snaps": [
      {
        "name": "pc",
        "type": "gadget",
        "default-channel": "24/stable",
        "id": "UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH"
      },
      {
        "name": "realtime-kernel",
        "type": "kernel",
        "default-channel": "24/stable",
        "id": "qOnOYvhgxU2fQhw3TgnjaSuOWhO2jDdm"
      },
      {
        "name": "core24",
        "type": "base",
        "default-channel": "latest/stable",
        "id": "dwTAh7MZZ01zyriOZErqd1JynQLiOGvM"
      },
      {
        "name": "snapd",
        "type": "snapd",
        "default-channel": "latest/stable",
        "id": "PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4"
      },
      {
          "name": "console-conf",
          "type": "app",
          "default-channel": "24/stable",
          "id": "ASctKBEHzVt3f1pbZLoekCvcigRjtuqw"
      }
    ]
  }

The console-conf snap added here is only to allow interactive user and network configuration. An image created for deployment at scale should not include that.

Inside an empty directory, create a file named model.json with the above content.

Change the following:

  • model to a name that accurately represents your device(s).

  • authority-id, brand-id to your developer ID, since this is custom model. Use snapcraft whoami command to get your developer ID.

  • timestamp to an RFC3339 formatted time set after the registration of your signing key. If you already have a registered key, use date -Iseconds --utc command to generate the current time. If not, do this in the next steps after registering your key.

  • store to your dedicated Snap Store ID.

The snaps array is a list of snaps that get included in the image. In that list, the realtime-kernel snap contains the realtime Linux kernel. Here you can add any other snaps, including for example your real-time applications.

Sign the model assertion

Next, we need to sign the model assertion. Refer to the guide on signing model assertion for details on how to sign the model assertion. Below are the needed steps:

  1. Create and register a key

    Use snapcraft list-keys to check your existing keys. If you don’t already have a key, create one locally and register it with your account. Choose a meaningful name for the key, as it can be used to sign multiple models. In this guide we use rtu-model to sign all our real-time Ubuntu models.

    snapcraft create-key rtu-model
    snapcraft register-key rtu-model
    

    Remember to update the model assertion’s timestamp, if you created a new key and plan to use it next.

  2. Sign the model assertion

    snap sign -k rtu-model model.json > model.signed.yaml
    

    The snap sign command takes JSON as input and produces YAML as output.

    Tip

    You need to repeat the signing every time you change the input model, because the signature is calculated based on the model.

Build the image

First, get familiar with the tooling by referring to the guide on building Ubuntu Core images.

We use ubuntu-image and need to set the paths to the following as input:

  • Exported store credentials

  • Signed model assertion YAML file

Export the store credentials to a file:

snapcraft export-login credentials.txt

Then build the image:

$ UBUNTU_STORE_AUTH_DATA_FILENAME=credentials.txt \
    ubuntu-image snap model.signed.yaml --verbose --validation=enforce
[0] prepare_image
Fetching snapd (21759)
Fetching realtime-kernel (153)
Fetching core24 (490)
Fetching pc (178)
Fetching console-conf (40)
WARNING: the kernel for the specified UC20+ model does not carry assertion max formats information, assuming possibly incorrectly the kernel revision can use the same formats as snapd
[1] load_gadget_yaml
[2] set_artifact_names
[3] populate_rootfs_contents
[4] generate_disk_info
[5] calculate_rootfs_size
[6] populate_bootfs_contents
[7] populate_prepare_partitions
[8] make_disk
[9] generate_snap_manifest
Build successful

The warning about assertion max formats can be safely ignored; see ubuntu-image assertion warning.

This downloads all the snaps specified in the model assertion and builds an image file called pc.img.

Hint

To fetch the realtime-kernel snap for this image build, it should be included explicitly in your dedicated Snap Store.

$ file pc.img
pc.img: DOS/MBR boot sector; partition 1 : ID=0xee, start-CHS (0x0,0,0), end-CHS (0x0,0,0), startsector 1, 6195199 sectors, extended partition table (last)

✅ The image file is now ready. Refer to Ubuntu Core guide on flashing the image to a storage medium.


After installing this image on your device, you can continue by tuning your system for real-time processing. The Modify boot parameters on Ubuntu Core guide describes the method for dynamically configuring the kernel command line parameters. The configuration is an iterative process that is best done together with the expected workload.

Once satisfied with the configurations, continue below to learn how those configurations can be set statically during the image build.

Create a custom real-time Ubuntu Core image

This section shows how to statically set the desired Kernel command-line parameters for the Ubuntu Core system. To do this, we need to create a custom gadget snap, create a model assertion, and then build the OS image.

Project directory

Start in an empty directory. We refer to this in different parts of the document as our project directory.

Create the gadget snap

The gadget snap documentation is a recommended read before starting.

This is best done by forking an existing reference gadget, then changing it for our purpose. For example, there is the pc gadget which is suitable for most AMD64 platforms, and the pi gadget which is meant for Raspberry Pis.

Inside the project directory, clone the specific branch of the pc-gadget repository and enter the repository:

git clone https://github.com/canonical/pc-gadget.git --branch=24 --depth=1
cd pc-gadget

Add the desired kernel command line in an array to kernel-cmdline.append in gadget/gadget-amd64.yaml. For example:

kernel-cmdline:
    append:
        - nohz=on
        - nohz_full=2-N
        - irqaffinity=0-1

Refer to Kernel boot parameters for the list of supported parameters.

Modify snapcraft.yaml to fit your application. At minimum, make sure to change the name and version to something distinct, for example, to realtime-pc and example respectively.

Now, build the gadget snap:

$ snapcraft --verbose
...
Created snap package realtime-pc_example_amd64.snap

Tip

You need to rebuild the snap every time you change the snapcraft.yaml file.

Create the model assertion

Create the model assertion inside the project directory. Follow the same steps in Create the model assertion section but replace the pc snap entry with the following:

{
    "name": "realtime-pc",
    "type": "gadget"
},

Unlike the original pc snap definition, this entry has no listed channel and id, because it isn’t published in a Store. The locally built gadget snap will be passed directly to the image builder. For production use, the gadget snap should be uploaded to a Store and then listed in the model assertion along with its channel and id. Uploading to the store makes it possible to use a signed snap that receives updates.

Sign the model assertion which has our custom realtime-pc gadget, using the same key which was created in the previous section of this guide:

snap sign -k rtu-model model.json > model.signed.yaml

Before we continue, let’s have an overview of the files inside our project directory:

$ tree -L 1
.
├── model.json
├── model.signed.yaml
└── pc-gadget

2 directories, 2 files

The project directory should contain the model assertion, the signed model assertion, and the pc-gadget directory.

Build the Ubuntu Core image

Similar to before, we use ubuntu-image to build the image. This time we also need to provide the path to the custom gadget snap file. We therefore need:

  • Exported store credentials

  • Signed model assertion YAML file

  • Locally built gadget snap

Build with the following command:

$ UBUNTU_STORE_AUTH_DATA_FILENAME=credentials.txt \
        ubuntu-image snap model.signed.yaml  --verbose --validation=enforce \
        --snap pc-gadget/realtime-pc_example_amd64.snap
[0] prepare_image
Fetching snapd (21759)
Fetching realtime-kernel (153)
Fetching core24 (490)
Fetching console-conf (40)
WARNING: the kernel for the specified UC20+ model does not carry assertion max formats information, assuming possibly incorrectly the kernel revision can use the same formats as snapd
WARNING: "realtime-pc" installed from local snaps disconnected from a store cannot be refreshed subsequently!
Copying "pc-gadget/realtime-pc_example_amd64.snap" (realtime-pc)
[1] load_gadget_yaml
[2] set_artifact_names
[3] populate_rootfs_contents
[4] generate_disk_info
[5] calculate_rootfs_size
[6] populate_bootfs_contents
[7] populate_prepare_partitions
[8] make_disk
[9] generate_snap_manifest
Build successful

The warning about assertion max formats can be safely ignored; see ubuntu-image assertion warning.

This adds all the snaps specified in the model assertion and builds an image file called pc.img. There is a warning for realtime-pc gadget snap because this is being side-loaded, rather than fetched from the store.

✅ The image file with the custom configurations is ready! Refer to Ubuntu Core guide on flashing the image to a storage medium.

After installing and running a device with this image, the kernel parameters can be verified by looking into /proc/cmdline:

$ cat /proc/cmdline
snapd_recovery_mode=run console=ttyS0,115200n8 console=tty1 panic=-1 nohz=on nohz_full=2-N irqaffinity=0-1

This guide provides a very basic setup to configure Ubuntu Core for real-time processing and create a bootable OS image for it. For production, the operating system configuration involves many more steps, such as network configuration and full disk encryption. The device will also need a serial assertion to authenticate itself and receive for example updates to the real-time kernel snap from a dedicated Snap Store.

The Ubuntu Core documentation is the best place to continue to learn about the various aspects.