Snap your real-time app¶
Now that you have your real-time app working, it’s time to package it. This section describes how to package your real-time app as a snap package.
Install requirements¶
Some dependencies are required when building snap packages:
- Confirm that you have snapd installed by running the - snap versioncommand. If you’re using Ubuntu, it should come pre-installed. Otherwise, refer to the snapd installation docs.
- Install the snapcraft snap: - sudo snap install --classic snapcraft 
Start to package¶
It is easier to create the snap by starting from a template.
Run the following command from the same directory where the source .c files reside:
ubuntu@ubuntu:~/tutorial$ snapcraft initGo to https://docs.snapcraft.io/the-snapcraft-format/8337 for more information about the snapcraft.yaml format.
Successfully initialised project.
This command creates a snap directory containing the snapcraft.yaml file.
Let’s examine the snapcraft.yaml file:
name: demo # you probably want to 'snapcraft register <name>'
base: core24 # the base snap is the execution environment for this snap
version: '0.1' # just for humans, typically '1.2+git' or '1.3.2'
summary: Single-line elevator pitch for your amazing snap # 79 char long summary
description: |
  This is my-snap's description. You have a paragraph or two to tell the
  most important story about your snap. Keep it under 100 words though,
  we live in tweetspace and your description wants to look good in the snap
  store.
grade: devel # must be 'stable' to release into candidate/stable channels
confinement: devmode # use 'strict' once you have the right plugs and slots
parts:
  my-part:
    # See 'snapcraft plugins'
    plugin: nil
You may also want to review the snapcraft.yaml schema, which is the reference for all the possible configuration keys for the snapcraft.yaml file.
Your working directory should now look like this:
ubuntu@ubuntu:~/tutorial$ tree.
├── cfs.c
├── edf.c
├── fifo.c
├── lock.c
├── snap
│   └── snapcraft.yaml
└── thread-affinity.c
2 directories, 6 files
You’ll need to make some modifications to the snapcraft.yaml file:
- Change the name from demo to rt-app. 
- Provide a meaningful summary and description of what the snap does. 
Define parts¶
Once the snap metadata boilerplate is in place, it’s time to define the parts.
In snaps, parts serve as the building blocks of the package, similar to a recipe’s list of ingredients and preparation steps.
Since you have five .c source files, you might assume that you need five separate parts.
While that approach is possible, it’s much easier to create a Makefile and use that to build all the programs.
Let’s create a Makefile for our real-time apps:
.PHONY: all install uninstall clean
CC := gcc
CFLAGS := -Wall -Wextra -Wno-unused-result -O2
TARGETS := cfs edf fifo lock thread-affinity
PREFIX ?= 
BINDIR := $(PREFIX)/bin
all: $(TARGETS)
%: %.c
	$(CC) $(CFLAGS) -o $@ $<
install: $(TARGETS)
	mkdir -p $(DESTDIR)$(BINDIR)
	install -m 755 $(TARGETS) $(DESTDIR)$(BINDIR)
uninstall:
	rm -f $(addprefix $(DESTDIR)$(BINDIR)/, $(TARGETS))
clean:
	rm -f $(TARGETS)
Because compiling sources with a Makefile is common, Snapcraft provides built-in plugins to simplify the process.
To list the available plugins for your chosen snap base (as defined in the base: field of snapcraft.yaml), run snapcraft plugins.
Since you build our C code using a Makefile, you can use the make plugin.
Tip
The make plugin relies on the DESTDIR variable being available and configurable in the Makefile.
It uses this variable to correctly place the built files inside the snap’s squashfs file.
We’re also going to rename the part in the template from my-part to something more meaningful, such as src.
Additionally, you need to specify the source location for Snapcraft to fetch the code.
This can be either a remote Git repository or local files within the project directory.
Since you want to build the .c files that in root of the project directory, you’ll set the source to .:
parts:
  src:
    plugin: make
    source: .
With this part defined, you can now build the snap:
ubuntu@ubuntu:~/tutorial$ snapcraft -vStarting snapcraft, version 8.7.3
Logging execution to '/home/ubuntu/.local/state/snapcraft/log/snapcraft-20250326-122052.993463.log'
lxd (5.21/stable) 5.21.3-75def3c from Canonical✓ installed
Launching managed ubuntu 24.04 instance...
Creating new instance from remote
Creating new base instance from remote
Creating new instance from base instance
Starting instance
Starting snapcraft, version 8.7.3
Logging execution to '/tmp/snapcraft.log'
Initialising lifecycle
Installing build-packages
Installing build-snaps
Pulling src
Building src
:: + make -j12
:: gcc -Wall -Wextra -Wno-unused-result -O2 -o cfs cfs.c
:: gcc -Wall -Wextra -Wno-unused-result -O2 -o edf edf.c
:: gcc -Wall -Wextra -Wno-unused-result -O2 -o fifo fifo.c
:: gcc -Wall -Wextra -Wno-unused-result -O2 -o lock lock.c
:: gcc -Wall -Wextra -Wno-unused-result -O2 -o thread-affinity thread-affinity.c
:: + make -j12 install DESTDIR=/root/parts/src/install
:: mkdir -p /root/parts/src/install/bin
:: install -m 755 cfs edf fifo lock thread-affinity /root/parts/src/install/bin
Staging src
Priming src
Packing...
Reading snap metadata...
Running linters...
Running linter: classic
Running linter: library
Creating snap package...
Packed rt-app_0.1_amd64.snap
The first time you build a snap, it may take longer because Snapcraft also downloads and installs a build provider (as seen in the logs). It also provisions an LXD container.
The build generates an artifact named rt-app_0.1_amd64.snap.
Each part of this name has a meaning:
- rt-app: The snap’s name, as defined in the- namefield of- snapcraft.yaml.
- 0.1: The snap’s version, as defined in the- versionfield of- snapcraft.yaml. This can be set dynamically based on a git tag or other information, as explained in this document.
- amd64: The architecture of the built snap. This matches the host’s architecture when doing a native build. The C programs and the snap can also be built for other architectures, for example using the Canonical build farm or by cross compiling the sources and manually setting the target snap architecture. It is also possible to cross compile using a plugin if the project used autotools.
Now you can verify that the real-time applications are packed into the snap by inspecting the contents of this .snap file.
Since a snap is a 
ubuntu@ubuntu:~/tutorial$ unsquashfs rt-app_0.1_amd64.snapParallel unsquashfs: Using 12 processors
6 inodes (6 blocks) to write
[==================================================================|] 12/12 100%
created 6 files
created 4 directories
created 0 symlinks
created 0 devices
created 0 fifos
created 0 sockets
created 0 hardlinks
This creates the squashfs-root directory with the following contents:
ubuntu@ubuntu:~/tutorial$ tree squashfs-root/squashfs-root/
├── bin
│   ├── cfs
│   ├── edf
│   ├── fifo
│   ├── lock
│   └── thread-affinity
└── meta
    ├── gui
    └── snap.yaml
4 directories, 6 files
Now that you’ve confirmed that the real-time app binaries are present, it’s time to define the apps.
Define apps¶
You created the snap but installing it will not expose any of the programs on the host. This is because you haven’t specified the apps within the snap.
Note
A snap doesn’t necessarily need to have apps defined—it can provide only files such as libraries or binaries. These are generally referred to as content snaps, as they use the content interface. That’s why it’s possible to build a snap successfully without defining any apps.
Create an apps section with one entry per app: cfs, edf, etc.
These are the names given to the apps, exposed on the system when installing this snap.
Each app requires a command.
The programs are inside the bin directory and you can define the relative path to them:
apps:
  cfs:
    command: bin/cfs
  edf:
    command: bin/edf
  fifo:
    command: bin/fifo
  lock:
    command: bin/lock
  thread-affinity:
    command: bin/thread-affinity
After defining the apps, you can rebuild the snap by running snapcraft -v again.
You can then install the snap using:
ubuntu@ubuntu:~/tutorial$ sudo snap install rt-app_0.1_amd64.snap --devmodert-app 0.1 installed
Note
Since the snap is being installed from a local snap bundle, you would typically install it in Dangerous Mode to bypass signature verification.
However, because the snap uses confinement: devmode, it must be installed using Developer Mode (--devmode).
This mode not only skips signature checks, but also allows installing snaps that use Developer Mode confinement.
Now, the snap appears in the list of installed snaps:
ubuntu@ubuntu:~/tutorial$ snap info rt-appname:      rt-app
summary:   Demo real time apps
publisher: –
license:   unset
description: |
  Package of a demo real time applications
commands:
  - rt-app.cfs
  - rt-app.edf
  - rt-app.fifo
  - rt-app.lock
  - rt-app.thread-affinity
refresh-date: 4 days ago, at 21:33 -03
installed:    0.1 (x1) 16kB -
Finally, you can run the defined real-time applications. Within a snap, the defined applications become application commands. Type the name of the installed snap and press the Tab key twice to list the available commands:
ubuntu@ubuntu:~/tutorial$ rt-app.rt-app.cfs              rt-app.edf              rt-app.fifo             rt-app.lock             rt-app.thread-affinity
Running the cfs program:
ubuntu@ubuntu:~/tutorial$ rt-app.cfsCalls made on thread1: 100000
Calls made on thread2: 1
Strictly confine the snap¶
In the previous section, you created a snap that works in Developer Mode. However, one of the key benefits of snap packages is their security policies can be enabled for software to run a in secure and strictly-confined sandboxed environment. To take advantage of these security policies, you must confine the snap.
The first step in confinement is understanding which system resources the applications need access to. Snap interfaces provide a mechanism to grant narrow access to specific system resources.
While there are a variety of resources and tools that exist for debugging snaps, we can identify these required resources needed for initial confinement with snappy-debug.
To install it:
ubuntu@ubuntu:~/tutorial$ sudo snap install snappy-debugsnappy-debug 0.36-snapd2.59.4 from Canonical✓ installed
Now, you need two terminals: one to run snappy-debug and another to execute the rt-app snap applications.
Running snappy-debug presents the following message:
ubuntu@ubuntu:~/tutorial$ snappy-debugINFO: Following '/var/log/syslog'. If have dropped messages, use:
INFO: $ sudo journalctl --output=short --follow --all | sudo snappy-debug
Tip
To ensure no log messages are lost, it is recommended to use the suggested journalctl command.
It needs to be run with sudo since accessing system logs requires privileged access:
sudo journalctl --output=short --follow --all | sudo snappy-debug
Analyze cfs application¶
- Application output: 
ubuntu@ubuntu:~/tutorial$ rt-app.cfsCalls made on thread1: 100000
Calls made on thread2: 1
- Debug output: 
No output from snappy-debug indicates that the cfs application does not require additional permissions
Analyze edf application¶
- Application output: 
ubuntu@ubuntu:~/tutorial$ rt-app.edfthread1:   period =  2 s
          runtime = 10 ms
         deadline = 11 ms
thread2:   period =  2 s
          runtime = 10 ms
         deadline = 11 ms
Calls made on thread1: 100000
Calls made on thread2: 1
- Debug output: 
= Seccomp =
Time: Mar 27 20:29:40
Log: auid=1000 uid=1000 gid=1000 ses=119 subj=snap.rt-app.edf pid=37555 comm="edf" exe="/snap/rt-app/x1/bin/edf" sig=0 arch=c000003e 314(sched_setattr) compat=0 ip=0x78846c90325d code=0x7ffc0000
Syscall: sched_setattr
Suggestion:
* add 'process-control' to 'plugs'
The seccomp message indicates that the application makes a sched_setattr system call.
As confirmed in the source code, this occurs inside the thread_start function.
The debug tool suggests adding the process-control interface.
To understand interfaces, plugs, and slots, refer to the interface management document.
To grant the necessary permission, add process-control to the plugs field under the edf app definition:
  edf:
    command: bin/edf
    plugs:
      - process-control
Rebuild and reinstall the snap, then connect the interface:
Note
You may see some logs on the snappy-debug terminal regarding lxd during the snapcraft build, those can be safely ignored.
sudo snap connect rt-app:process-control
Listing snap connections confirms the manual connection:
ubuntu@ubuntu:~/tutorial$ snap connections rt-appInterface        Plug                    Slot              Notes
process-control  rt-app:process-control  :process-control  manual
Analyze fifo and lock applications¶
Repeat the same process done for cfs and edf applications on this one.
Like occurred for cfs, when running fifo application no reports were generated by snappy-conf.
The same happens for the lock application, no reports were generated.
Analyze thread-affinity application¶
Repeating the process for thread-affinity something different happens on the logs:
- Application output: 
ubuntu@ubuntu:~/tutorial$ rt-app.thread-affinitythread1 priority: 0
thread2 priority: 0
Calls made on thread1: 100000
Calls made on thread2: 1
- Debug output: 
= Seccomp =
Time: Mar 27 21:04:22
Log: auid=1000 uid=1000 gid=1000 ses=119 subj=snap.rt-app.thread-affinity pid=40684 comm="thread-affinity" exe="/snap/rt-app/x2/bin/thread-affinity" sig=0 arch=c000003e 203(sched_setaffinity) compat=0 ip=0x733af5a4d6e1 code=0x7ffc0000
Syscall: sched_setaffinity
Suggestion:
* ignore the denial if the program otherwise works correctly (unconditional sched_setaffinity is often just noise)
You see a seccomp report, but no suggested interface to use. Instead there is a message suggesting to just ignore the denial report. This happens because there are some denials coming from the security policies defined in the snap sandboxing, which potentially do not affect the functionality of an application. This is the case for this app.
Tip
During the confinement process for your real-time applications, you used snappy-debug to identify the necessary security policies. This approach is especially effective for smaller applications or when you do not have access to the source code. If your application follows a single, fixed execution path, debugging tools alone may suffice. However, if it supports multiple configurations, each triggering different execution paths, a comprehensive source code analysis is preferable. Such analysis should identify all required files, directories, and syscalls and may include reviewing access to Linux-specific resources such as procfs, sysfs, and configfs.
The optimal approach depends on your application’s complexity and whether you have access to its source code.
Conclude strict confinement¶
Now that the required interfaces are defined, change the confinement property to strict:
confinement: strict
Then:
- Build the snap like done before with - snapcraft.
- Install it. This time, since you aren’t using - devmodeanymore, it’s possible to install it in a confined way. Because the snap isn’t being installed from a snap store, there is no way to fully trust it. It is therefore necessary to acknowledge the dangerous nature of it. So you run our install command with the- --dangerousflag.- ubuntu@ubuntu:~/tutorial$- sudo snap install ./rt-app_0.1_amd64.snap --dangerous- rt-app 0.1 installed 
- Connect the - process-controlinterface as shown before. It might still be connected if the snap wasn’t uninstalled during the development steps.
- Run all the applications. You may see some logs on the - snappy-debugterminal. It’s necessary to pay attention to the field- profile=for AppArmor denials and- exe="for Seccomp denials. If it says:- profile="/usr/lib/snapd/snap-confine", it’s related to the- snap-confinecomponent and not about the- rt-appsnap. To learn more about this, take a look at this reference of the snap system architecture.
You have now finished the development of your snap.
You can change the grade field to stable if you consider it so.
Snaps with the devel grade cannot be promoted to a lower risk level.
This is related to the concept of snap channels, important when publishing a snap.
Conclusion¶
The final snapcraft.yaml file should look like this:
name: rt-app
base: core24
version: '0.1'
summary: Demo real time apps
description: |
  snap package with some demo real time applications
grade: stable
confinement: strict
parts:
  src:
    plugin: make
    source: .
apps:
  cfs:
    command: bin/cfs
  edf:
    command: bin/edf
    plugs:
      - process-control
  fifo:
    command: bin/fifo
  lock:
    command: bin/lock
  thread-affinity:
    command: bin/thread-affinity
A good next step is to distribute this snap via a store:
