Abusing Docker to be a VM

This post is to describe how I got a virtual 32-bit linux machine running inside docker on my 64-bit mac book (2013), mostly as a way for me to remember how I did it. I basically just started using Docker, and I’m surely using it wrong…

I’m currently at Recurse Center (formerly known as Hacker School), a sort of writers retreat for programmers. The last couple of weeks I was working on the trap-tracing tool that I have mentioned in previous blog posts. Since this is written as a very low-level linux tool, I was working on my old 32-bit linux netbook.

Well, my netbook died, so in order to continue working on my main mac os machine, I figured I’d just try to run my tools in a 32-bit linux virtual machine on my mac.

Given that I’m at RC, I asked for suggestions on which VM to use. Google told me to try use virtualbox, but Stanley Zheng told me I should give something called Docker a try, because it’s more light weight.

It turns out Docker is not a VM, and apparently you’re not really supposed to pretend it’s one. Its purpose appears to be to run applications inside a sandboxed, pre-defined linux environment, for example to help deploying code in clouds.

But it is possible to abuse Docker to get a simple linux VM on my Mac, and with some pairing, Stanley and I ensured that the Linux emulation is accurate enough that the trap-tracing tool works (which uses the x86 trap flag and signal handlers).

Later I set up a docker 32-bit Linux container on my machine. Here’s what I did:

1) Install docker

I just followed steps 1, 2 on this page. Since I don’t care about web development I didn’t go further into the tutorial.

2) Download an image, and set up a container:

adubra@mo:~$ docker run -it --name linux32 -v ~/linux32:/usr/local/linux32 32bit/ubuntu:16.04 bash
  Unable to find image '32bit/ubuntu:16.04' locally
  16.04: Pulling from 32bit/ubuntu
  50f85dcd8f29: Pull complete
  Digest: sha256:eeb557f736be17c5a8706176746e0e94c49853ca32d840e4ad06819f22415ddc
  Status: Downloaded newer image for 32bit/ubuntu:16.04
  (...)

This command is an alias for several commands: it tells docker to create and run a container from the image named “32bit/ubuntu:16.04”, which is the 32-bit linux I’m using. Since this image doesn’t exist in the local Docker repository, it will also go and download it and add it to the local repository of images (use docker images to see the images in your repository).

As I understand it, a container is a particular instance of an image, it represents a machine that you can run code on. And as I understand it, it is pretty light-weight because its file system will only store the difference from the image. So you could spin plenty of containers, for example every time you start some server. Which is the usually the intended purpose. But I’m creating a container that I will re-use and run software on. So I gave it the name linux32 using the —name flag.

I also want to share some of host and the container file system, that’s why I did using the -v flag, which binds directories using the syntax -v host_path:container_path.

I’d really like to bind this into the home directory of a user, but that user doesn’t exist. So if I bind into /home/some_user/my_dir, then it will create issues later when adding that user inside the container (because the directory will already exist and be owned by the wrong user).

Lastly, the -it flag tells Docker to start an interactive terminal session, and bash tells it to execute Bash.

So overall, this command will download the image, install the image, create a container from it, start the container, and enter the container, providing an interactive bash shell inside the container.

3) Add my user inside the container

I’m not sure why I shouldn’t just run everything as root, but it feels wrong somehow. So I’ll create a user:

root@a54e562265f0:/# adduser adubra
Adding user `adubra' ...
Adding new group `adubra' (1000) ...
(...)

4) Allow my user to execute sudo

When executing sudo from my newly created user, for example when installing software inside the container using apt-get install, I’m running into password issues. I can override these passwords request by adding an override to the sudoers file:

root@a54e562265f0:/# echo 'adubra  ALL=NOPASSWD: ALL' > /etc/sudoers.d/sudo_override

5) Exit the container …

…by running exit.

That’s basically it. Now I can see the list of containers that exist (but are not necessarily running) using:

adubra@mo:~$ docker ps -a
  CONTAINER ID  IMAGE               COMMAND  CREATED    STATUS              NAMES
  a54e562265f0  32bit/ubuntu:16.04  "bash"   9 min ago  Exited (0) 23s ago  linux32

Before I can re-enter the linux box again, I have to start the container:

adubra@mo:~$ docker start linux32
linux32

Now I can enter it by executing bash interactively, telling it I want to run as my user:

adubra@mo:~$ docker exec -it --user adubra linux32 bash
adubra@a54e562265f0:/$

And in the /usr/local/ directory I fill find the link to my host directory.

Dockerfiles

After talking to another fellow RC participant, Robert Hunt, I found he’s also using Docker to emulate a Linux machine on his Mac. He pointed out to me that I’m doing a lot of setup inside the container, and if I screwed it up or deleted it accidentally, I’d have to do it all over again.

He told me that one can create new images that will have all the programs and users that I want by writing a dockerfile. It’s a script that allows assembling a new image from an existing one, that includes all the necessary setup code.

Apparently a dockerfile is supposed to be called “Dockerfile” (like Makefiles, I guess?). It’s extending an existing image using FROM <image>:<tag>. Afterwards we can install software using RUN <command>. Adding users is explained in this Stackoverflow question.

A simple Docker script that basically does all the stuff we did above (and also installs some basic Linux tools) would look like this:

FROM 32bit/ubuntu:16.04

RUN apt-get update
RUN apt-get install -y \
    man \
    ssh \
    build-essential

RUN echo 'adubra  ALL=NOPASSWD: ALL' > /etc/sudoers.d/sudo_override
RUN useradd -ms /bin/bash adubra
USER adubra
WORKDIR /home/adubra

We can then build the image using

adubra@mo:~/docker$ docker build -t linux32_image .

Now it will show up as an image Docker knows about:

adubra@mo:~/docker$ docker images
  REPOSITORY      TAG        IMAGE ID        CREATED         SIZE
  linux32_image   latest     6aa83d83383f    2 minutes ago   361 MB
  32bit/ubuntu    16.04      cc30c7e0cb5a    5 months ago    321 MB

Using this linux32_image for the Docker run command, everything should be set up already. And now it’s also possible to bind the host directory directory into the home directory of the user using the -v flag to the run command, because that home directory will already exist.

In the end, it seems like Docker is a viable way to have a Linux VM running on a Mac. Although I’m still not sure how to get GUI tools running, and I still don’t really understand how I can have a 32-bit Linux if Docker doesn’t use real VMs.