1--- 2title: Portable Services Introduction 3category: Concepts 4layout: default 5SPDX-License-Identifier: LGPL-2.1-or-later 6--- 7 8# Portable Services Introduction 9 10systemd (since version 239) supports a concept of "Portable Services". 11"Portable Services" are a delivery method for system services that uses 12two specific features of container management: 13 141. Applications are bundled. I.e. multiple services, their binaries and all 15 their dependencies are packaged in an image, and are run directly from it. 16 172. Stricter default security policies, i.e. sand-boxing of applications. 18 19The primary tool for interacting with Portable Services is `portablectl`, 20and they are managed by the `systemd-portabled` service. 21 22Portable services don't bring anything inherently new to the table. All they do 23is put together known concepts to cover a specific set of use-cases in a 24slightly nicer way. 25 26## So, what *is* a "Portable Service"? 27 28A portable service is ultimately just an OS tree, either inside of a directory, 29or inside a raw disk image containing a Linux file system. This tree is called 30the "image". It can be "attached" or "detached" from the system. When 31"attached", specific systemd units from the image are made available on the 32host system, then behaving pretty much exactly like locally installed system 33services. When "detached", these units are removed again from the host, leaving 34no artifacts around (except maybe messages they might have logged). 35 36The OS tree/image can be created with any tool of your choice. For example, you 37can use `dnf --installroot=` if you like, or `debootstrap`, the image format is 38entirely generic, and doesn't have to carry any specific metadata beyond what 39distribution images carry anyway. Or to say this differently: the image format 40doesn't define any new metadata as unit files and OS tree directories or disk 41images are already sufficient, and pretty universally available these days. One 42particularly nice tool for creating suitable images is 43[mkosi](https://github.com/systemd/mkosi), but many other existing tools will 44do too. 45 46Portable services may also be constructed from layers, similarly to container 47environments. See [Extension Images](#extension-images) below. 48 49If you so will, "Portable Services" are a nicer way to manage chroot() 50environments, with better security, tooling and behavior. 51 52## Where's the difference to a "Container"? 53 54"Container" is a very vague term, after all it is used for 55systemd-nspawn/LXC-type OS containers, for Docker/rkt-like micro service 56containers, and even certain 'lightweight' VM runtimes. 57 58"Portable services" do not provide a fully isolated environment to the payload, 59like containers mostly intend to. Instead, they are more like regular system 60services, can be controlled with the same tools, are exposed the same way in 61all infrastructure, and so on. The main difference is that they use a different 62root directory than the rest of the system. Hence, the intent is not to run 63code in a different, isolated environment from the host — like most containers 64would — but to run it in the same environment, but with stricter access 65controls on what the service can see and do. 66 67One point of differentiation: since programs running as "portable services" are 68pretty much regular system services, they won't run as PID 1 (like they would 69under Docker), but as normal processes. A corollary of that is that they aren't 70supposed to manage anything in their own environment (such as the network) as 71the execution environment is mostly shared with the rest of the system. 72 73The primary focus use-case of "portable services" is to extend the host system 74with encapsulated extensions, but provide almost full integration with the rest 75of the system, though possibly restricted by security knobs. This focus 76includes system extensions otherwise sometimes called "super-privileged 77containers". 78 79Note that portable services are only available for system services, not for 80user services (i.e. the functionality cannot be used for the stuff 81bubblewrap/flatpak is focusing on). 82 83## Mode of Operation 84 85If you have a portable service image, maybe in a raw disk image called 86`foobar_0.7.23.raw`, then attaching the services to the host is as easy as: 87 88``` 89# portablectl attach foobar_0.7.23.raw 90``` 91 92This command does the following: 93 941. It dissects the image, checks and validates the `os-release` file of the 95 image, and looks for all included unit files. 96 972. It copies out all unit files with a suffix of `.service`, `.socket`, 98 `.target`, `.timer` and `.path`. whose name begins with the image's name 99 (with `.raw` removed), truncated at the first underscore if there is one. 100 This prefix name generated from the image name must be followed by a ".", 101 "-" or "@" character in the unit name. Or in other words, given the image 102 name of `foobar_0.7.23.raw` all unit files matching 103 `foobar-*.{service|socket|target|timer|path}`, 104 `foobar@.{service|socket|target|timer|path}` as well as 105 `foobar.*.{service|socket|target|timer|path}` and 106 `foobar.{service|socket|target|timer|path}` are copied out. These unit files 107 are placed in `/etc/systemd/system.attached/` (which is part of the normal 108 unit file search path of PID 1, and thus loaded exactly like regular unit 109 files). Within the images the unit files are looked for at the usual 110 locations, i.e. in `/usr/lib/systemd/system/` and `/etc/systemd/system/` and 111 so on, relative to the image's root. 112 1133. For each such unit file a drop-in file is created. Let's say 114 `foobar-waldo.service` was one of the unit files copied to 115 `/etc/systemd/system.attached/`, then a drop-in file 116 `/etc/systemd/system.attached/foobar-waldo.service.d/20-portable.conf` is 117 created, containing a few lines of additional configuration: 118 119 ``` 120 [Service] 121 RootImage=/path/to/foobar.raw 122 Environment=PORTABLE=foobar 123 LogExtraFields=PORTABLE=foobar 124 ``` 125 1264. For each such unit a "profile" drop-in is linked in. This "profile" drop-in 127 generally contains security options that lock down the service. By default 128 the `default` profile is used, which provides a medium level of security. 129 There's also `trusted`, which runs the service with no restrictions, i.e. in 130 the host file system root and with full privileges. The `strict` profile 131 comes with the toughest security restrictions. Finally, `nonetwork` is like 132 `default` but without network access. Users may define their own profiles 133 too (or modify the existing ones). 134 135And that's already it. 136 137Note that the images need to stay around (and in the same location) as long as the 138portable service is attached. If an image is moved, the `RootImage=` line 139written to the unit drop-in would point to an non-existent path, and break 140access to the image. 141 142The `portablectl detach` command executes the reverse operation: it looks for 143the drop-ins and the unit files associated with the image, and removes them. 144 145Note that `portablectl attach` won't enable or start any of the units it copies 146out by default, but `--enable` and `--now` parameter are available as shortcuts. 147The same is true for the opposite `detach` operation. 148 149The `portablectl reattach` command combines a `detach` with an `attach`. It is 150useful in case an image gets upgraded, as it allows performing a `restart` 151operation on the units instead of `stop` plus `start`, thus providing lower 152downtime and avoiding losing runtime state associated with the unit such as the 153file descriptor store. 154 155## Requirements on Images 156 157Note that portable services don't introduce any new image format, but most OS 158images should just work the way they are. Specifically, the following 159requirements are made for an image that can be attached/detached with 160`portablectl`. 161 1621. It must contain an executable that shall be invoked, along with all its 163 dependencies. Any binary code needs to be compiled for an architecture 164 compatible with the host. 165 1662. The image must either be a plain sub-directory (or btrfs subvolume) 167 containing the binaries and its dependencies in a classic Linux OS tree, or 168 must be a raw disk image either containing only one, naked file system, or 169 an image with a partition table understood by the Linux kernel with only a 170 single partition defined, or alternatively, a GPT partition table with a set 171 of properly marked partitions following the 172 [Discoverable Partitions Specification](DISCOVERABLE_PARTITIONS.md). 173 1743. The image must at least contain one matching unit file, with the right name 175 prefix and suffix (see above). The unit file is searched in the usual paths, 176 i.e. primarily /etc/systemd/system/ and /usr/lib/systemd/system/ within the 177 image. (The implementation will check a couple of other paths too, but it's 178 recommended to use these two paths.) 179 1804. The image must contain an os-release file, either in `/etc/os-release` or 181 `/usr/lib/os-release`. The file should follow the standard format. 182 1835. The image must contain the files `/etc/resolv.conf` and `/etc/machine-id` 184 (empty files are ok), they will be bind mounted from the host at runtime. 185 1866. The image must contain directories `/proc/`, `/sys/`, `/dev/`, `/run/`, 187 `/tmp/`, `/var/tmp/` that can be mounted over with the corresponding version 188 from the host. 189 1907. The OS might require other files or directories to be in place. For example, 191 if the image is built based on glibc, the dynamic loader needs to be 192 available in `/lib/ld-linux.so.2` or `/lib64/ld-linux-x86-64.so.2` (or 193 similar, depending on architecture), and if the distribution implements a 194 merged `/usr/` tree, this means `/lib` and/or `/lib64` need to be symlinks 195 to their respective counterparts below `/usr/`. For details see your 196 distribution's documentation. 197 198Note that images created by tools such as `debootstrap`, `dnf --installroot=` 199or `mkosi` generally satisfy all of the above. If you wonder what the most 200minimal image would be that complies with the requirements above, it could 201consist of this: 202 203``` 204/usr/bin/minimald # a statically compiled binary 205/usr/lib/systemd/system/minimal-test.service # the unit file for the service, with ExecStart=/usr/bin/minimald 206/usr/lib/os-release # an os-release file explaining what this is 207/etc/resolv.conf # empty file to mount over with host's version 208/etc/machine-id # ditto 209/proc/ # empty directory to use as mount point for host's API fs 210/sys/ # ditto 211/dev/ # ditto 212/run/ # ditto 213/tmp/ # ditto 214/var/tmp/ # ditto 215``` 216 217And that's it. 218 219Note that qualifying images do not have to contain an init system of their 220own. If they do, it's fine, it will be ignored by the portable service logic, 221but they generally don't have to, and it might make sense to avoid any, to keep 222images minimal. 223 224If the image is writable, and some of the files or directories that are 225overmounted from the host do not exist yet they will be automatically created. 226On read-only, immutable images (e.g. squashfs images) all files and directories 227to over-mount must exist already. 228 229Note that as no new image format or metadata is defined, it's very 230straightforward to define images than can be made use of in a number of 231different ways. For example, by using `mkosi -b` you can trivially build a 232single, unified image that: 233 2341. Can be attached as portable service, to run any container services natively 235 on the host. 236 2372. Can be run as OS container, using `systemd-nspawn`, by booting the image 238 with `systemd-nspawn -i -b`. 239 2403. Can be booted directly as VM image, using a generic VM executor such as 241 `virtualbox`/`qemu`/`kvm` 242 2434. Can be booted directly on bare-metal systems. 244 245Of course, to facilitate 2, 3 and 4 you need to include an init system in the 246image. To facilitate 3 and 4 you also need to include a boot loader in the 247image. As mentioned, `mkosi -b` takes care of all of that for you, but any 248other image generator should work too. 249 250The 251[os-release(5)](https://www.freedesktop.org/software/systemd/man/os-release.html) 252file may optionally be extended with a `PORTABLE_PREFIXES=` field listing all 253supported portable service prefixes for the image (see above). This is useful 254for informational purposes (as it allows recognizing portable service images 255from their contents as such), but is also useful to protect the image from 256being used under a wrong name and prefix. This is particularly relevant if the 257images are cryptographically authenticated (via Verity or a similar mechanism) 258as this way the (not necessarily authenticated) image file name can be 259validated against the (authenticated) image contents. If the field is not 260specified the image will work fine, but is not necessarily recognizable as 261portable service image, and any set of units included in the image may be 262attached, there are no restrictions enforced. 263 264## Extension Images 265 266Portable services can be delivered as one or multiple images that extend the base 267image, and are combined with OverlayFS at runtime, when they are attached. This 268enables a workflow that splits the base 'runtime' from the daemon, so that multiple 269portable services can share the same 'runtime' image (libraries, tools) without 270having to include everything each time, with the layering happening only at runtime. 271The `--extension` parameter of `portablectl` can be used to specify as many upper 272layers as desired. On top of the requirements listed in the previous section, the 273following must be also be observed: 274 2751. The base/OS image must contain an `os-release file`, either in `/etc/os-release` 276 or `/usr/lib/os-release`, in the standard format. 277 2782. The upper extension(s) image(s) must contain an extension-release file in 279 `/usr/lib/extension-release.d/`, with an `ID=` and `SYSEXT_LEVEL=`/`VERSION_ID=` 280 matching the base image. 281 2823. The base/OS image does not need to have any unit files. 283 2844. The upper extension(s) image(s) must at least contain one matching unit file each, 285 with the right name prefix and suffix (see above). 286 2875. As with the base/OS image, the upper extension(s) image(s) must be a plain 288 sub-directory, a btrfs subvolume or a raw disk image. 289 290``` 291# portablectl attach --extension foobar_0.7.23.raw debian-runtime_11.1.raw foobar 292# portablectl attach --extension barbaz_7.0.23/ debian-runtime_11.1.raw barbaz 293``` 294 295## Execution Environment 296 297Note that the code in portable service images is run exactly like regular 298services. Hence there's no new execution environment to consider. And, unlike 299Docker would do it, as these are regular system services they aren't run as PID 3001 either, but with regular PID values. 301 302## Access to host resources 303 304If services shipped with this mechanism shall be able to access host resources 305(such as files or AF_UNIX sockets for IPC), use the normal `BindPaths=` and 306`BindReadOnlyPaths=` settings in unit files to mount them in. In fact the 307`default` profile mentioned above makes use of this to ensure 308`/etc/resolv.conf`, the D-Bus system bus socket or write access to the logging 309subsystem are available to the service. 310 311## Instantiation 312 313Sometimes it makes sense to instantiate the same set of services multiple 314times. The portable service concept does not introduce a new logic for this. It 315is recommended to use the regular systemd unit templating for this, i.e. to 316include template units such as `foobar@.service`, so that instantiation is as 317simple as: 318 319``` 320# portablectl attach foobar_0.7.23.raw 321# systemctl enable --now foobar@instancea.service 322# systemctl enable --now foobar@instanceb.service 323… 324``` 325 326The benefit of this approach is that templating works exactly the same for 327units shipped with the OS itself as for attached portable services. 328 329## Immutable images with local data 330 331It's a good idea to keep portable service images read-only during normal 332operation. In fact all but the `trusted` profile will default to this kind of 333behaviour, by setting the `ProtectSystem=strict` option. In this case writable 334service data may be placed on the host file system. Use `StateDirectory=` in 335the unit files to enable such behaviour and add a local data directory to the 336services copied onto the host. 337 338## Links 339 340[`portablectl(1)`](https://www.freedesktop.org/software/systemd/man/portablectl.html)<br> 341[`systemd-portabled.service(8)`](https://www.freedesktop.org/software/systemd/man/systemd-portabled.service.html)<br> 342[Walkthrough for Portable Services](https://0pointer.net/blog/walkthrough-for-portable-services.html)<br> 343[Repo with examples](https://github.com/systemd/portable-walkthrough) 344