Setting up Linux VM on Apple Silicon for Docker

Piyush Agrawal
4 min readDec 11, 2020

If you’re like me, you were impatient to get your hands on the new M1 Mac and are now wondering how to make the most of it while official support for apps and tools catches up. I am starting back with programming after a hiatus of seven years. To begin with, docker was on top of my list of things.

I started looking for ways to run a Ubuntu VM on Linux to host a docker instance. I found a couple of solutions where folks figured how to run a Ubuntu VM on M1 — vftool and SimpleVM. I will use vftool for this walkthrough because I can run it directly from a terminal.

Step 1: Download and build vftool

The following set of commands will download vftool from the git repository and build it.

$ git clone git@github.com:evansm7/vftool.git
$ cd vftool
$ make
$ cd build

You can go ahead and install vftool or, like me, use it from the build directory.

Step 2: Setup the VM

I used Ubuntu Focal cloud image, but you can go for your favorite distro. First, let’s download the image, initrd, and the kernel. I downloaded all of them to the vftool build directory for ease of execution.

$ cd vftool/build
$ curl -O https://cloud-images.ubuntu.com/releases/focal/release/ubuntu-20.04-server-cloudimg-arm64.tar.gz
$ tar xvfz ubuntu-20.04-server-cloudimg-arm64.tar.gz
$ curl -O https://cloud-images.ubuntu.com/focal/current/unpacked/focal-server-cloudimg-arm64-vmlinuz-generic
# unzip the vmlinuz
$ mv focal-server-cloudimg-arm64-vmlinuz-generic focal-server-cloudimg-arm64-vmlinuz-generic.gz
$ gunzip focal-server-cloudimg-arm64-vmlinuz-generic.gz
$ curl -O https://cloud-images.ubuntu.com/focal/current/unpacked/focal-server-cloudimg-arm64-initrd-generic

With help of droidx, I was able to setup the image file for use with vftool. First, we start the VM without specifying the root.

$ ./vftool -k focal-server-cloudimg-arm64-vmlinuz-generic -i focal-server-cloudimg-arm64-initrd-generic  -d focal-server-cloudimg-arm64.img -m 4096 -a "console=hvc0"

You should see an output like following -

2020-12-10 22:38:55.343 vftool[90732:2443383] vftool (v0.3 10/12/2020) starting2020-12-10 22:38:55.343 vftool[90732:2443383] +++ kernel at vmlinuz-5.4.0-54-generic.efi.signed, initrd at focal-server-cloudimg-arm64-initrd-generic, cmdline 'root=/dev/vda1 console=hvc0', 1 cpus, 4096MB memory2020-12-10 22:38:55.344 vftool[90732:2443383] +++ fd 3 connected to /dev/ttys0042020-12-10 22:38:55.344 vftool[90732:2443383] +++ Waiting for connection to:  /dev/ttys004

In a new terminal window use the screen command to connect the /dev/ttys004 (this device is random, use what you get from the output). The VM will finish booting up with (initramfs) prompt

$ screen /dev/ttys004
...
...
...
(initramfs)

Then run the following to create a root mount point, set the root password to root, disable cloud init, and set a static IP.

mkdir /mnt
mount /dev/vda /mnt
chroot /mnt

touch /etc/cloud/cloud-init.disabled

echo 'root:root' | chpasswd

cat <<EOF > /etc/netplan/01-dhcp.yaml
network:
ethernets:
enp0s1:
dhcp4: true
addresses: [192.168.64.39/24]
version: 2
EOF

exit
umount /dev/vda

Kill the VM with Ctrl+C or Ctrl+D, and then run the VM properly with the root. Before running the VM, we will resize it so we can make better use of it. The dd command below will resize the image to 100,000 MB.

$ dd if=/dev/zero bs=1m count=100000 >> ./focal-server-cloudimg-arm64.img
$ ./vftool -k focal-server-cloudimg-arm64-vmlinuz-generic -i focal-server-cloudimg-arm64-initrd-generic -d focal-server-cloudimg-arm64.img -m 4096 -a "root=/dev/vda console=hvc0"

Connect to the VM on a new terminal window with screen. Login using root (password: root). Let’s confirm if the resize worked. Run df -h | grep vda to check the size of /dev/vda. If it is 1.3G instead of what you resized too, let’s verify through parted if the dd command worked.

$ parted
(parted) print devices
/dev/vda (106GB)
(parted) quit

If the output above shows the correct size that you expanded to, run resize2fs /dev/vda to resize the partition to max. Running df -h should now show the correct size.

Keep the VM running.

Step 3: Install Docker on Ubuntu and Configure Ubuntu

Run the following to install docker on your Ubuntu VM -

$ sudo apt-get update
$ sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
$ sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository "deb [arch=arm64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io

Test the docker installation by running docker run hello-world.

Run ifconfig | grep 192 to ensure the IP address is set up correctly.

Step 4: Install and Configure Docker on Mac

Now, let’s switch to a new Mac terminal window. First, let’s set up SSH using a key. Run the following to create a key pair (if you don’t have one already), and push your public key to the Ubuntu VM through SSH.

$ ssh-keygen -t rsa
$ cat ~/.ssh/id_rsa.pub | ssh root@192.168.64.39 "mkdir -p ~/.ssh && touch ~/.ssh/authorized_keys && chmod -R go= ~/.ssh && cat >> ~/.ssh/authorized_keys"

If you run into issues with pushing the key to the VM. Try doing it manually. On your Mac terminal, run cat ~/.ssh/id_rsa.pub and copy the output. It will be a long string that starts with ssh-rsa and ends with username@machinename.

Switch to Ubuntu VM terminal to add the key at the end of the file~/.ssh/authorized_keys.

Next, install docker on Mac through the binaries: https://docs.docker.com/engine/install/binaries/

Once docker is installed, create a docker context on your Mac with host set to Ubuntu VM. Use the IP retrieved from Ubuntu using ifconfig command above.

$ sudo docker context create m1docker --docker "host=ssh://root@192.168.64.39"
$ docker context use m1docker

Run docker run hello-world if the docker context is working properly.

And there we have it!

--

--