At this point, Flatpak is a mature system for deploying and running desktop applications. It has accumulated quite some sophistication over time, which can make it appear more complicated than it is.
In this post, I’ll try to look in depth at some of the core concepts behind Flatpak, namely runtimes and extensions.
In the beginning: bundles
At its very core, the idea behind Flatpak is to bundle applications with their dependencies, and ship them as a self-contained unit. There are good reasons that bundling is attractive for application developers:
- There is a much bigger chance that the app will run on an arbitrary end-user system, which may have different versions of libraries, different themes, or a different kernel
- You are not relying on all the different update mechanisms and policies of Linux distributions
- Distribution updates to your dependencies will not break your app behind your back
- You can test the same code that your users run
Best of both worlds: runtimes
In the age-old debate beween bundlers and packagers, there are good arguments on both sides. The usual arguments against bundling are:
- Code duplication. If a library gets hit by a security issue you have to fix it in all the apps that bundle it
- Wastefulness. if every app ships an entire library stack, this blows up the required bandwidth for downloads and the required disk space for installing them
With this in mind, Flatpak early on introduced the concept of runtimes. The idea behind runtimes is that many desktop applications use a deep library stack, but it is often a similar set of libraries. Therefore, it makes sense to take these common library stacks and distribute them separately as “GNOME runtime” or “KDE runtime”, and have apps declare in their metadata which runtime they need.
It then becomes the responsibility of the flatpak tooling to assemble the applications filesystem tree with the runtimes filesystem tree when it creates the sandbox environment that the app runs in.
To avoid conflicts, Flatpak requires that the applications filesystem tree is rooted in /app, while runtimes have a traditional /usr tree.
Splitting off runtimes preserves most of the benefits that I outlined for bundles, while greatly reducing code duplication and letting us update libraries independently of applications.
Of course, it also brings back some of the risks of modularity: updating the libraries independently carries, once again, the risk of breaking the applications that use the runtime. So the team maintaining a runtime has to be very careful to avoid introducing problematic changes or incompatibilities.
Going further: extensions
As I said, shipping runtimes separately saves a lot of bandwidth, since the runtime has to be downloaded only once for all the applications that share it. But a runtime is still a pretty massive download, and contains a lot of things that may not be useful most of the time or are just optional.
A good examples for this are translations. It is not uncommon for desktop apps to be translated in 50 locales. But the average user will only ever use a single one of these. In traditional packaging, this is sometimes addressed by breaking translations out as “lang packs” that can be installed separately.
Another example is debug information. You don’t need symbols and other debug information unless you encounter a crash and want to submit a meaningful stacktrace. In traditional packaging, this is addressed by splitting off “debuginfo” packages that can be installed when needed.
Flatpak provides a mechanism to address these use cases. Runtimes (and applications too) can declare extension points, which are designated locations in their filesystem tree where additional runtimes can be mounted. These additional runtimes are called extensions. When constructing a sandbox for running an app, flatpak tooling will look for matching extensions and mount them at the right place.
Flatpak is not a generic solution, but tailored towards the use case of desktop applications, and it tries to do the the right thing out of the box: flatpak-builder automatically breaks out .Locale and .Debug extensions when building apps or runtimes, and when installing things, flatpak installs the matching .Locale extension. But it goes beyond that and only installs the subset of it that is relevant for the current locale, thereby recreating the space-saving effect of lang packs.
Extensions: infinite variations
The extension mechanism is flexible enough to cover not just locales and debuginfo, but all sorts of other optional components that applications might need. To give just some examples:
- OpenGL drivers that match the GPU
- Other hardware-specific APIs like vaaapi
- Media codecs
- Widget themes
All of these can be provided as extensions. Flatpak has the smarts built-in to know whether a given OpenGL driver extension matches the hardware or whether a given theme extension matches the current desktop theme, and it will automatically install and use matching extensions.
At last: the host OS
The examples in the previous paragraph are realized as extensions because the shared objects or theme components need to match the runtime they are used with.
But some things just don’t change very much over time, and don’t need exact matching against the runtime to be used by applications. Examples in this category are fonts, icons or certificates.
Flatpak makes these components from the host OS available in the sandbox, by mounting them below /run/host/ in the sandbox, and appending /run/host/share to the XDG_DATA_DIRS environment variable.
Flatpak does a lot of hard work behind the scenes to ensure that the apps it runs find an environment that looks similar to a traditional Linux desktop, by combining the application, its runtime, optional extensions and host components.
The Flatpak documentation provides more information about working with Flatpak as an application developer.