Desktop Docker (2/3): Secure Linux Graphical Containers

This is my second post in a series I’m doing on using graphical applications in Docker containers. If you missed part 1, here’s the flow of the 3-part series:

  1. Desktop Docker (1/3): Linux Graphical Containers
  2. Desktop Docker (2/3): Secure Linux Graphical Containers
  3. Desktop Docker (3/3): GPU-enabled Linux Graphical Containers

With this post I’ll discuss how to run graphical applications more securely in Docker containers. In the last post, I discussed passing the X Windows information into a container and how it wasn’t a secure way of completing the objective since we’ve poked a hole through our containerized isolation. Although the content I’m going to discuss here is more secure, it’s also more complex since there are additional variables involved. But, if security is paramount, it can be worth the effort.

The way to more securely isolate graphical containers is to run them in separate X Windows display servers, run as a non-root user within the container and reduce capabilities. There’s a cool utility called x11docker that can assist with the task. x11docker focuses on security and takes additional steps to reduce threats, but also makes adding functionality to a container easier. The security of your container will be directly related to how much it needs to rely on the host’s services/processes/drivers. For more thorough x11docker security information go here.

Before we get into the install and use of x11docker, we should probably touch on the whole idea of Wayland, as x11docker can accommodate it. Wayland is a newer display technology for Linux (mostly), designed to replace the dated and aging X Windows architecture. Many of the newer Linux distro versions have already started adopting it in some form or fashion. In my previous post I mentioned that I run OpenSUSE Leap 15, which has adopted Wayland and can use it as its default display technology with certain configurations. Wayland is not directly compatible with X Windows, and since there are so many apps that are written for X, and relatively few written for Wayland, compatibility technology has been developed — namely Xwayland. So, when we’re discussing running graphical applications in Docker containers, we really need to understand if we’re needing to run the app in X Windows, in Wayland, or in X Windows THROUGH Wayland and configure accordingly. In my case, I’m running an NVIDIA graphics card with NVIDIA’s proprietary driver, which currently is not compatible with Wayland. It IS possible to run Wayland on my system, just not as the main display technology and not hardware accelerated.

x11docker is actually a shell script that does the job of taking a ton of options, simplifying them and making a (more) secure running environment, and it’s really good at telling you when a choice you’ve made has reduced security. It’s also really good at making display technology choices for you, based on the technologies you have installed. To give you an idea of the broad technology support offered by x11docker, here’s a list of the main supported display technologies:

  • Xpra
  • Xephyr
  • nxagent
  • hostdisplay
  • Xorg
  • weston
  • Xwayland

Plus certain combinations. See this list if you’re interested, but understanding these technologies is not required.

So the first thing one needs to do, since x11docker is just a shell script that spawns other technology, is to install the actual technology. If you want to use Xpra, you need to install Xpra, and the same for most of the rest of the technologies. Your native package manager should readily help with that, but that said, there’s a good chance some of these technologies are already installed — hostdisplay uses your native OS display capabilities (most likely X Windows, and is comparable to the method used in my last post); xorg is also probably installed. If you want to use a separate X Windows display server, install Xpra, Xephyr and/or NXagent. And depending on the app you’re trying to containerize, you may want/need to try running under multiple technologies to see what works best. For example, one app I was containerizing utilized the QT windowing framework. I found it to work and display best in Xpra.

Installing x11docker is easy — after all, it’s just a shell script. Before doing that though, you might decide if you want to have the x11docker-gui. The GUI utilizes the kaptain technology, and if it’s missing natively, x11docker will use a kaptain docker container. So, if you want to install kaptain natively, have a look here. The shortest/fastest way to install x11docker is linked here. It’s literally just downloading the file and then running the --install or --update options for the script.

After installing, as mentioned, you have a lot of options. One main option is, do you want to run in desktop mode, or seamless mode? Seamless mode makes the app look/feel like it is installed natively, while desktop mode creates a window that equates to a desktop and then creates application windows within the desktop window — kindof/sortof a similar feel to a virtual machine. If you are looking for the easy way of doing things and just want to get going, you can let x11docker choose the technology to use. There is a --desktop parameter that will change its decision between desktop and seamless mode, but that’s really all that’s required. If you’re a control freak and want to control every decision, you’ll need to determine which display technology supports the mode you want to run in. For that, take a look at this page.

Next, what I found to be valuable, is to run the x11docker-gui and start looking at the options. Start simple, and add options as you find value in previous selections. In order for it to be of value, you will need a Docker image with a GUI application installed. So let’s go through the process:

  • First, let’s cheat and use Jess Frazelle’s Firefox Docker container. The main directory can be found here, but to make things simple, download the following three files to a new directory:
  • Before we build the Docker image, we need to tweak a couple things in the Dockerfile.
    • Change this:

      RUN echo ‘pref(“browser.tabs.remote.autostart”, false);’ >> /etc/firefox/syspref.js

      COPY entrypoint.sh /usr/bin/startfirefox

      ENTRYPOINT [ “startfirefox” ]

      to this:

      COPY entrypoint.sh /usr/bin/startfirefox

      RUN echo ‘pref(“browser.tabs.remote.autostart”, false);’ >> /etc/firefox/syspref.js \
      && chmod 755 /usr/bin/startfirefox

      ENTRYPOINT [ “/usr/bin/startfirefox” ]

       

  • Build the Docker image with the following command, replacing <your name> with your name:
    docker build -t <your name>/firefox:56.0.2 .
  • Before we get to running containers … some thoughts:
    • When running x11docker-gui, start it from a terminal window so you can see its output. It can offer huge benefit to determine what’s actually going on. For example, if you don’t have dependencies for one of the options, the terminal output will say what’s missing and tell you what x11docker is falling-back to, if it is, in fact, falling-back to another option. If you’re having issues with an image and want more output, you can toggle the --verbose or --debug parameters from the “Terminal output” tab on the right-side of the interface.
    • x11docker always runs containers with the docker --rm parameter, meaning that when the application terminates, the container will be discarded as well. This works well for these type of desktop applications, ensuring that every run is a golden image run, and cannot be changed. That said, you CAN persist data between container runs. To do that, directories from the host should be shared with the container. The way this is done can vary based on your use case. Take a look at the x11docker --home or --sharedir parameters and proceed accordingly.
  • Now, let’s run x11docker-gui:
    x11docker-gui

    • Either select <your name>/firefox:56.0.2 from the “Docker image” dropdown menu, or type it in, and select your X server of choice — I used --xpra for this activity.
    • Now click the Run button
      Shortly after that, you should see the normal Firefox window appear as a seamless application, but running securely in a separate X instance. Closing the window will terminate the application and the container.
    • Next, in the “Regular Options” tab on the right-side of the GUI, check the --pulseaudio checkbox (assuming you’re using PulseAudio on your system) and click Run again. This time you should be able to get sound from your container, so crank up those speakers and navigate to some cool site! Close the window when you’re done. Note: The Dockerfile used to create the Firefox image included the necessary PulseAudio libraries for this option to work. Check the documentation if you’re curious about the prerequisites of the various options.
    • Iterate your testing with different options. One example would be to enable printing; after all, who doesn’t occasionally need to print from their web browser?
    • Once you’ve iterated to get the container working the way you want, you can use the “Copy to clipboard” button to get the x11docker command to put into a shell script, or click the “Create starter on desktop” to create a .desktop file for your containerized way of running your application.
    • Close the GUI when you’re done with it.

I’d like to point out that using the --gpu option may require some additional action, depending on the hardware you’re running. For example, if you’re using an AMD card or an NVIDIA card with the open source nouveau driver, things are pretty easy and x11docker can accommodate. But if you’re using NVIDIA’s proprietary drivers your docker container needs to have the exact same version of the driver for the --gpu option to work properly. There are two different ways of dealing with that; 1) install the driver into your image, or 2) let x11docker install the driver automagically into the CONTAINER each time it starts. The former is more of a set-it-and-forget-it option, but if you upgrade your host’s driver you’ll have to rebuild your images. The latter is slower at container start (tens of seconds), but can dynamically change to accommodate driver version variations. To do the latter, copy NVIDIA’s runfile for the proper version of the driver to the ~/.local/share/x11docker directory. Again, x11docker’s console output will tell you what’s going on, especially with the --verbose option.

Hopefully that’s enough to get you started with x11docker and running secure Docker containers. Check back for part 3 of the series if you’re interested in utilizing more advanced NVIDIA GPU capabilities inside your Docker containers. And if you’ve found this blog helpful or have questions, follow the discourse link below and let me know!

Let's Innovate Together

Just ask us how we can make a difference for you today.