Fun with Containers - Episode One - Firefox

Word of warning: Please do not assume that following this tutorial will add anything to your internet security. I created this guide because I couldn’t find anything about running Firefox in its own container.

A quick suggestion: When I was trying to find out about containers and namespaces, I made the mistake of web searching with a search string that would include the word, “container.” This turned out to be a painful experience since most web searches would return information on Docker. Unfortunately, I did not realize this until I had already written this blog post. In future web searches, I will be replacing the word “container” with the word “namespace.”

Why do this at all? Lots of reasons. You can try out a new extension without risking your main Firefox app. If you’re a web developer you can test out your app in its own Firefox container. The possibilities are limitless. Have fun.

Firefox in a basket

Let’s start with the basics.

Download the Alpine MINI ROOT FILESYSTEM from alpinelinux.org.

Copy the tarball to your directory of choice to a container directory and then extract it. For this guide, I will be using the directory /opt/containers/alpine. You can use whatever directory you like.

Copy your resolv.conf into the container directory.

cp /etc/resolv.conf /opt/containers/alpine/etc/

For best security practices we are going to mount only a few files from /dev into the containers directory.

mkdir /opt/containers/alpine/dev/snd
touch /opt/containers/alpine/dev/urandom
touch /opt/containers/alpine/dev/null
sudo mount -o bind /dev/snd /opt/containers/alpine/dev/snd
sudo mount -o bind /dev/null /opt/containers/alpine/dev/null
sudo mount -o bind /dev/urandom /opt/containers/alpine/dev/urandom

Next, we are going to use the unshare command to start ash shell.

unshare -muipCUfr --kill-child --mount-proc --root=/opt/containers/alpine env - /bin/ash -l

Unshare is similar to chroot, except that we are able to isolate child processes from the rest of the host system. For example, when we use top to view our processes in the new container we can see that we are only running ash and top, and ash has a PID of 1.

All right. So now we are in our own isolated container. It’s time to install Firefox.

apk update
apk add firefox font-noto 

We are just about done, but to make things a bit easier for us let’s create a short script that will automate a few things for us. In the container directory, in my case /opt/containers/alpine create a file named start.sh. Inside start.sh add the following.

#!/bin/sh
hostname localhost
export HOSTNAME=localhost
export USER=firefox
export DISPLAY=:0
firefox

Now we are ready to run an isolated Firefox.

unshare -muipCUfr --kill-child --mount-proc --root=/opt/containers/alpine env - sh start.sh

In the process we used to make the container, we did not mount the device files needed for hardware acceleration. We also didn’t install the files from the package manager to enable hardware acceleration. This was deliberate, as I wanted to mount the smallest amount of files in the /dev directory. The total size of our container is 399M.

In the next post, we will dissect the arguments for unshare and learn more about namespaces.

Stay tuned!