Containerized desktop applications with podman
Everybody is talking about containers these days, however most of the discussion revolves around use cases in the context of server applications. Today I’d like to discuss a maybe slightly unusual, but nonetheless interesting use case for containers: running Linux desktop applications within a container with otherwise unchanged look & feel. To add some spice to the mix, I will explain this at the example of podman (I could equally well have chosen docker, but I like true open source software).
Desktop applications in a container? Why would I want this?
Well, first of all one of the key container value propositions is relevant in a desktop application context too: keeping the application and all its dependencies together in one, well – container, allows keeping the host file system lean & clean and free from potential versioning conflicts (just to mention a recent example I ran into – the widely used Anaconda Python library, which combines hundreds of libs for scientific purposes, brings along its own Qt which tends to conflict with the one that your distro installs. Not good but the world is full of examples like this.). It allows running apps on the same machine that for example require different versions of the same library, which otherwise would require special tweaks during installation or might not be possible at all (there’s usually a way, but we all want things to just work instead of fighting with these awkward problems don’t we?).
It also enables you to cleanly get rid of an installed program without any residuals silently being left behind. Last but not least, it can help if the app you want isn’t provided by your distro or the package the maintainer provides isn’t built for your distro so you that you’re unsure whether it is a good idea to install it or not.
It provides a certain level of safety if you install 3rd party packages that you do not completely trust to not break your installation by putting files in strange locations, adding additional versions of libraries to your path that might confuse other applications, and so on. It also adds an additional layer of security, since software running in a container normally does not see the rest of the file system or other running processes outside the container. There are ways to make sure only software of known origin goes into your container, but that’s a topic for another blog post.
At this point I’d like to mention that if the package you’re looking for isn’t provided by your distro (and you are using a community distro like openSUSE), please consider contributing a package. It is easier than you think (stay tuned for a future blog on this) and it helps making the distro you like even better also for others. Goes without saying, right?
Last but not least, it is actually pretty easy and it provides an opportunity to gain hand-on experience with containers while doing something useful that every Linux user needs on a daily basis.
So how does it work?
It could not be simpler to be honest. I’ll explain this at the example of an openSUSE Tumbleweed host, so instead of using docker we’ll be using podman and cri-o. If you prefer to use docker you should be able to use exactly the same commands since podman and docker use almost exactly the same syntax and semantics. I’m using cri-o since openSUSE Kubic also has recently moved to cri-o as the default container runtime. If you’re interested in further information on this move, check out this blog post by my amazing colleague Richard Brown.
First we have to write a Dockerfile to build our container. For this first very simple example we will use xeyes as our test application. We use the official opensuse Leap base image from the Docker public registry (there’s no need to configure that since podman uses it by default. You can see the configure registries and their priority order with podman info).
RUN zypper refresh \
&& zypper -n in xeyes
RUN useradd –create-home –home-dir /home/chuck chuck
CMD [ “xeyes” ]
After specifying the base image in the first line, we install the application with the usual zypper command one would use on the host just the same way. We also create a user “chuck” within the container (a user with that name must exist on the host as well) and his home directory so that we can run our application without root privileges, and tell the container to run the app from the home directory under that user name. This is not a must, but it provides us with some extra security. We’ll come back to this in a second. Then, the only thing missing is to tell the container what command to run at startup. Simple as that.
Now we can build the container with
# podman build ––rm ––force-rm -t localhost/xeyes .
The –rm and –force-rm switches are to ensure intermediate containers are removed and only the final container is kept on the system.
Before we run the container, we have to allow the X Server to accept a remote connection, but we want to limit this to processes running on the same host and by a specific user. First we need to become root (rootless podman is being very close to completion, I’ll be back on this soon).
# xhost local:chuck
Now we can run the container and see what we get. To make sure the application running in the container automatically connects to the X Server on the host, we have to give the container access to /tmp/.X11-unix in the host filesystem and import the DISPLAY variable.
# podman run -d \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e DISPLAY=unix$DISPLAY \
–name xeyes \
And voila, we have those friendly eyes reminding us of the good old times showing up on our screen. Let’s have a look if our trick with running the container as non-root worked.
# ps aux | grep xeyes
chuck 21971 0.4 0.0 32164 3748 ? Ss 14:32 0:00 xeyes
root 22073 0.0 0.0 6896 816 pts/0 S+ 14:33 0:00 grep –color=auto xeyes
It did! Although we ran the container as root, the xeyes process runs under the user “chuck”. Let’s have a look at the output of “podman ps”.
# podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
475d3091e260 localhost/xeyes:latest xeyes 5 minutes ago Up 5 minutes ago xeyes
Once we close the xeyes window, let’s see what happened to the container.
# podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
It’s gone! Really? Well, to be precise, the container is not running anymore, but it still exists. You can check that with the command podman inspect xeyes, which will show information on both the image and the container named xeyes. If you remove the container with podman rm xeyes, the same command will only show information on the image we created with our podman build command above. This is because the command podman run is in fact a shortcut which combines creation, start, attaching and running a command in a container into a one-liner. If you’d only create a container, it would be ready to run but not running. You could then start it with podman start, attach to it and run the xeyes command inside the container, and then detach again, which would put your system into exactly the same state.
A nice side-effect of this logic is that after the initial run command above, we can now run our xeyes program with a much simpler command than the somewhat bulky multi-line snippet above.
# podman start xeyes
And the eyes are back!
So much for now, stay tuned for more tips and tricks around running desktop applications in containers. I’m for example working on getting Spotify to run in a container (getting it connect to the host’s sound card is a bit of a challenge), and I’m playing around with some openCV stuff which also nicely benefits from container isolation.