CaaC: Container as a Condom

Protect your operating system from unintended consequences

All those dirty prerequisites

We all know containers are great and can minimize a bunch of issues that used to bedevil us just a few years ago. One thing containers are great at is sparing us the need of installing software directly on our operating systems. Want to benchmark your code against all of the n+ types of Ruby implementations? Need to install (yet) another NodeJS tool that the self-proclaimed “cool” kids at your company are switching to? Got contract work at a bank that still uses Java SE 6? (I’m not even kidding.) You don’t want to infect your personal/development machine with all those intrusive organisms.

For completeness sake, be informed that there are alternatives to containers when it comes to cross-platform, self-contained packages—namely, Flatpak, AppImage and Snapcraft. In fact, those alternatives are typically better suited for the task at hand. Snapcraft, for instance, installs self-contained packages inside /snap, which we can easily mount on a separate partition and throw away at any point without affecting the rest of the system.

Sometimes, though, the software we want to install is not available for any of those new package managers. Some other times all we want is the final executable, not the libraries required to generate it. Granted, you could learn how to build your own snap or Flatpak package, but in the event that you have a deadline and need to stick to what you already know, CaaC will be your best bet.

In this series, I’ll focus on the two use cases described above:

  • software that’s not yet available as an isolated, self-contained package;
  • software that needs to be generated in a specific way and requires companion libraries during compilation.

Also, to keep things simple, I’ll restrict myself to Docker only, but the same principles may be applied to other container engines.

“Do you have protection?”

You bet I do! I’ll start this section by showing you how to dockerize Gnuplot (as of today, there’s no Gnuplot available in the Snap Store or FlatHub). I’ll also show you how to build a container that you can later use as an SDL development environment—many games I find on the web require manual compilation before I can run them on my Linux boxes. Let’s get this going, then.

Gnuplot

This is a simple Dockerfile you can use for Gnuplot:

FROM alpine

RUN apk --no-cache add \
    --repository https://dl-3.alpinelinux.org/alpine/edge/testing/ \
    gnuplot

ENTRYPOINT ["gnuplot"]

--no-cache is used in order to tell apk to keep its index updated. We also need to specify the apk repository (--repository) where the Gnuplot package can be found.

Creating a Docker image out of this Dockerfile is pretty straightforward:

docker build --tag gnuplot --tag gnuplot:alpine .

Because I want this image to be the default Gnuplot image on my system, I’m using two tags so that I can reference the image both as gnuplot:alpine and the shorter gnuplot.

Running GUI applications in Docker

Using this image requires a little trick, because Gnuplot is a GUI application and relies on a windowing system. On Linux, the first thing we need to do is enable our current user to create connections to the host’s X11 server:

$ xhost +local:$(whoami)
non-network local connections being added to access control list

In case your Docker installation requires that you run the docker command as the root user, change that line to xhost +local:root.

If you don’t do that, you’ll probably get a gnuplot: unable to open display error when trying to execute a plot. Don’t worry—that change will only be effective during your user’s session. You can always revert that command with xhost -local:$(whoami).

One way of making X11 work from within a Docker container is by tricking the X11 client in the container into thinking that the X server is located at localhost, i.e. in the container itself. For that to work, we first need to set the DISPLAY environment variable in the container to match the value that DISPLAY contains in the host machine. That’s usually going to be :0, but I don’t recommend you hard code it like that. The second step is mounting the host’s X11 Unix domain socket onto the container so that the container can get direct access to the socket file located on the host machine. That will effectively let Gnuplot communicate with the X server on the host machine as if the X server was located inside the container itself.

Let’s see it in practice. To save us from unnecessary typing, I’ll wrap the command in an alias:

alias gnuplot="docker run \
--rm \
-it \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e DISPLAY=unix$DISPLAY \
--name gnuplot \
gnuplot"

At this point, you can simply call gnuplot and start having fun. Here’s a plot script you can execute to confirm that everything is working as expected:

set title "Simple Function Plot"
set xrange [-3.14159:3.14159]
set yrange [-1.1:1.1]
set xlabel "theta"
set ylabel "sin(theta)"
set label "sin(0.0)" at 0.0, 0.0
plot sin(x)

And this is what I get when I run it from my Dockerized Gnuplot:

/img/2018/08/2018-08-28-caac-container-as-a-condom/01-gnuplot-thumb.jpg

Dockerized Gnuplot using host machine's X server.

SDL2 development environment

SDL2 (Simple DirectMedia Layer) is “a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.” SDL has always been popular among hardcore indie game developers, but more and more mainstream games are being written in it after Steam’s release.

In order to run SDL2 applications on your computer, all you have to do is install the libsdl2* packages. On my Ubuntu box, for instance, I do that through the package manager:

sudo apt -y install libsdl2-2.0-0 libsdl2-image-2.0-0 libsdl2-mixer-2.0-0

That’s all great if you’re lucky enough to find a pre-packaged game for your operating system. More often than not, though, we need to compile the game from source, which requires that the SDL2 development libraries (libsdl2-*-dev) be installed on our machines as well. I don’t like the idea of installing all those dependencies just for compilation purposes, and that’s where our second condom comes into play:

FROM ubuntu:16.04

RUN apt update
RUN apt install -y \
    software-properties-common \
    build-essential \
    make \
    cmake \
    libsdl2-dev \
    libsdl2-gfx-dev \
    libsdl2-image-dev \
    libsdl2-mixer-dev \
    libsdl2-net-dev \
    libsdl2-ttf-dev

WORKDIR /sdl2dev
ENTRYPOINT ["make"]

This Dockerfile installs the development libraries necessary to compile the majority of SDL2 indie games you’ll find on the web. The entry point command defaults to make because Make is what most SDL game developers use as their build tool.

To generate a new Docker image, use the following command:

docker build --tag sdl2dev .

We can now test the new image with a publicly available game to see if we can compile it. For this example, I’ll download and build uMario, which defines a build target in its Makefile:

git clone git@github.com:jakowskidev/uMario_Jakowski.git uMario
cd uMario
docker run --rm -v $PWD:/sdl2dev sdl2dev build

That should generate a build subfolder on your host machine’s current directory, where you can run the game from:

cd build
./uMario
/img/2018/08/2018-08-28-caac-container-as-a-condom/02-sdl2-thumb.jpg

SDL2 game compiled in a Dockerized development environment.

Conclusion

Container as a Condom is an easy and safe way to run software on your personal or development machines and there are no excuses not to use it. It may not feel natural at first but, given enough time, I’m pretty sure you’ll get used to it!