Portable services for legacy daemons
Learn how to use systemd portable services to bundle applications into OS trees or disk images. This method uses portablectl to manage units with security profiles like strict or nonetwork, offering a lightweight alternative to Docker for legacy daemons.
Portable services provide a practical way to deliver bundled applications directly from an image. This approach uses application bundling and stricter security policies. A portable service constitutes an OS tree inside a directory or a raw disk image. You can build these images with dnf --installroot, orbootstrap, or mkosi. The image format remains generic and carries no new metadata beyond what distribution images already provide. These images can be directory trees, btrfs subvolumes, or raw disk images with GPT or MBR partition tables.
They do not provide a fully isolated environment. Most containers intend to isolate the payload, but portable services run in the same environment as the host. They use a different root directory instead.
It runs as processes.
It uses host resources.
The image must contain an executable and all dependencies. Any binary code must be compatible with the host architecture. The tree must also include /usr/lib/os-release or /etc/os-release. The image must contain at least one matching unit file with the right name prefix and suffix.
The attach operation follows a specific sequence. The tool dissects the image and validates the os-release file. It searches for all included unit files. It copies these files to /etc/systemd/system.attached/, which is part of the normal unit file search path of PID 1. For every unit file, a drop-in is created. This drop-in contains RootImage= and Environment=PORTABLE=.
Management and profiles
portablectl handles the interaction with these services. When you run portablectl attach, the tool dissects the image to find unit files. It copies files like .service, .socket, .target, .timer, and .path to /etc/systemd/system.attached/. It then adds a drop-in with a RootImage= line.
The command links profiles.
Available profiles include:
| Profile | Security Level |
|---|---|
| default | medium |
| trusted | no restrictions |
| strict | toughest restrictions |
| nonetwork | medium security without network access |
The naming convention relies on the image filename. If you have an image named foobar_0.7.23.raw, the prefix is foobar. The tool looks for unit files matching foobar-*.service, foobar@.service, or foobar.*.service.
You might find the setup tedious. Building images with mkosi requires a proper distro image as a base.
Managing these units feels familiar to you. You can use systemctl list-unit-files to see them. You can enable, disable, start, or stop them using standard commands. You can also use systemctl edit to extend them. The detach command executes the reverse operation. It looks for the drop-ins and the unit files and removes them. The inspect command extracts metadata and shows whether an image is attached. It displays the os-release data and the unit files’ contents when combined with --all. Does the portablectl command work for rootless users?
Deployment and updates
Updates become easier with portablectl reattach. This command combines detach and attach to swap an image. It performs a restart instead of a stop and start. This minimizes downtime for running units.
One specific benefit involves socket activation. Systemd binds the listening socket before the application starts. It then passes the file descriptor to the application. This allows for zero-downtime restarts.
The app runs under systemd. You can use systemd-analyze security to tune sandboxing.
You should understand your deployment needs before switching. If you want to build images with the convenience of Docker, you can use pivot_root within a namespace to run commands. This technique allows you to package Python apps with native dependencies. You can use mksquashfs to compress the rootfs into a single file. A SquashFS image is often 50% to 70% smaller than an OCI image.
The process involves several steps. You first download and extract a minimal rootfs tarball. Then you use --user and --map-root-user in a new user namespace to run commands inside. If you use a command to build an image, you can use pivot_root to atomically swap the filesystems, which ensures that a compromised build cannot touch your actual system beyond any bind mounts.
The reattach command is particularly useful when an image gets upgraded. It allows performing a restart operation on the units instead of stop plus start. This provides lower downtime and avoids losing runtime state associated with the unit such as the file descriptor store.
Building images for Python apps requires careful planning. Many Python packages, like scipy or pandas, ship wheels with native dependencies. However, packages like scikit-sparse link against CHOLMOD, which is GPL. Because of this, you must include the system library in your image. This requires building a real OS tree with a package manager.