As an avid container user and occasional distro hopper Distrobox has my interest peaked.
Containers, containers and more containers
My main development setup is based on a read-only openSUSE MicroOS installation, meaning my host system has few if any tools or non-essential software installed. I can, and sometimes do, re-use my home as well which becomes a lot easier when it also has my tools and apps inside it. Plus the fact that updates to apps or development environments don’t really disturb anything else.
For this reason I’m also somewhat fond of the fact that distrobox is very distro-agnostic and officially supports being installed to your user’s home:
curl -s https://raw.githubusercontent.com/89luca89/distrobox/main/install | sh -s -- --prefix ~/.local
Note: Be sure that your prefix of choice is in your PATH
!
A couple of things to consider
Since I like things confined to userspace my primary use case is also running everything as a regular user. Naturally that means your system has to be setup for it. It’s easy but also potentially hella confusing when you haven’t done that, so let’s make sure we have usable ID mappings before getting further into it:
sudo usermod --add-subuids 524288-1878523903 --add-subgids 524288-1878523903 $USER
grep $USER /etc/sub{u,g}id
podman system migrate
The range we are choosing here is reserved for containers by systemd, which is relevant when dynamic users such as managed by systemd-homed come into play. If you’re interested in the details podman docs is where it’s at.
Although you can use other container managers I’m going to assume that you use podman or that you can swap out the executable in relevant commands going forward.
In the name of convenience it may be a good idea to also define your default image so that you don’t need to specify it unless you specifically want something else:
export DBX_CONTAINER_IMAGE=registry.opensuse.org/opensuse/tumbleweed:latest
Any time a new container is created distrobox will use this image.
How does this work then?
The first time you use a container it needs to be created. Although distrobox will also prompt you to do this when you’re trying to enter a non-existent container this is handy to use in e.g. setup scripts you might have to prepare your environment on a new machine or for a new user:
distrobox create
This will pull an image if needed and populate it with some necessary tooling. Next, as will the instructions also tell you, enter:
distrobox enter
And that’s it. If all you needed is a container that mirrors your user setup with generous access to files and host access for networking, devices or your graphical session you’re good to go.
sudo make me a container
Another reason you may need to create a container explicitly is… superuser powers. It’s not the primary use case but now and then real root is useful. If like me you’re into graphical prompts via polkit you can trivially enable them by overriding the sudo helper:
DBX_SUDO_PROGRAM="pkexec" distrobox create --root
I like my services managed by systemd
What’s I think a unique feature at this point is that you can get support for systemd within containers out of the box. By default containers are isolated as you would expect. A command-line switch enables them:
distrobox create --init
systemctl list-units
Now you can not only develop your services inside a container but allow it to communicate with the init system running on the host system. This can otherwise be rather tedious to sort out.
Note: Check that your preferred image supports it, though, some may not have systemd installed. The official Tumbleweed and Leap images do.
Save me the hassle, please
One thing you actually get from the container runtime that’s nevertheless quite handy is saving and restoring of images. Besides always having the option to recreate containers from scratch you can in fact back them up:
# Backup a snapshot of your container
podman container commit -p mybox myimage
podman save myimage:latest | gzip > myimage.tar.gz
# Restore your existing snapshot and make a container from it
podman load < myimage.tar.gz
distrobox create --image myimage:latest
It does help, though, to remember to do this before breaking an image. If you forget to do so you’re back to distrobox create
. 😉️
Architectures are not that big of a deal
If that wasn’t impressive enough yet, how about running images on different architectures? Again this is strictly speaking relying on your container runtime and Linux kernel magic. Assuming you have a suitable qemu installed (i.e. qemu-uefi-aarch64
on openSUSE) you can actually do this on an x86 machine:
distrobox ephemeral -a "--arch=arm64"
The -a
actually passes container manager-specific arguments like in this case the architecture. Assuming the image provides it, it will just do what you would expect.
Oh, and in case you’re wondering… I was creating an ephemeral container there. This is handy for when you just need another clean container that you don’t intend on keeping around. Like its persistent cousin it honors DBX_CONTAINER_IMAGE
so it’ll automatically use your default image unless you specify --image
. In the above case it will get the arm64 version of Tumbleweed since that’s what we specified earlier.
Convenient integration into my workflow
If you use a multiplexer like zellij adding distrobox to your default.kdl
can make life even easier:
tab name="distrobox" {
pane {
command "distrobox"
args "enter"
close_on_exit true
}
}
In order to simplify development workflows where some of your tools live on the host system, or perhaps other containers, I also use a command not found handler that’s aware of the hybrid nature of the system:
command_not_found_handle() {
# do not run in a pipe
[ ! -t 1 ] && return 127
# use command-not-found if installed
command -v cnf >/dev/null 2>&1 || cnf() { echo $1: command not found; }
# elevate sudo priviledges if needed
if [ -x "/usr/sbin/$1" ]; then
sudo /usr/sbin/$*
elif [ -e /run/.containerenv ]; then
# try to execute the command on the host
distrobox-host-exec "${@}" || (cnf $1; return 127)
else
cnf $1; return 127
fi
}
[ -n "${ZSH_VERSION-}" ] && command_not_found_handler() { command_not_found_handle; }
On openSUSE there’s the scout-command-not-found
package to provide cnf
which looks up the package containing the command you attempted to run when it wasn’t actually installed. This mechanism is leveraged here to do two more things. First priviledged commands are looked up so you don’t need to type sudo unnecessarily when there’s no other choice. Second distrobox-host-exec
is used to call into the host system. This makes things like xdg-open
, podman
and flatpak
work from within the container.
Incidentally this also works with non-distrobox containers that have access to the distrobox tooling and it’s more generic than flatpak-spawn --host
.
My appetite is wet, what’s next?
You can find many more details on the neat tricks we discussed on the distrobox project page.
I also wrote about toolbox before which comes bundled with MicroOS and might be worth a look as well.
Now go enjoy your containers!