Docker Isn't Cross Platform. It's Just Really Good at Faking It.

Docker doesn't magically run Linux containers on macOS or Windows. Here is what's actually happening under the hood.

You're on your Mac or Windows machine, and one day you hear about Docker. You go to the Docker website and run your first container: hello-world. You're happy and delighted seeing "Running your container on Mac" pop up, but is it really true? Is your Mac or Windows actually the one running the containers?

I seriously doubted this when I read about how containerization works.

A brief on how containers work

A container is a bundle of your software packaged up to run in all machines, regardless of its OS or preloaded dependencies. A container image is a packaged filesystem (tarball of layers). A running container is a live process using that filesystem.

Containerization makes use of the Linux kernel. A container is essentially a collection of Linux processes.

Very broadly containers utilize two kernel features namely Namespaces and cgroups(Control Groups), although many other features such as pivot_root, seccomp-bpf etc are being used but for this blog we’ll keep our scope to only these two features.

Namespace: Consider a school with students divided into different classes. Each class has its own roll number system, its own supplies, its own little world that's the Namespace. The students are the processes living inside it, and each one genuinely believes their classroom is the whole school.

cgroups: It dictates the amount of shared resources a process can use, they make sure no class (process in our example) hogs more than its designated share of CPU or memory

So combining Namespaces and cgroups one can create their own isolated environment to run process and it would feel like running an entirely new system with its own resources, without the hassle of running Virtual Machines.

All of this Namespaces, cgroups is Linux kernel machinery. Your Mac's kernel isn't Linux, neither is Windows. So none of this should be able to run natively on either, which is exactly where the real twist comes in.

The Reveal

Back to our original question, so what’s happening if Mac/Windows aren’t the one running all your containers, it seems there’s a whole invisible Linux VM running in the background you can’t see. Don’t trust me? just type “docker info” in your Terminal, what do you see?

Where did this Linux come from, I haven’t downloaded this!

This is the VM I am talking about, when you command docker on your terminal, it is not talking to your Mac, it interacts with the Docker daemon present in your VM.

Docker Daemon(dockerd) is the one listening to the docker commands and then acts accordingly, managing containers, images and all

Mac and Windows manages this using their own products that they have developed.

Apple has built its own virtualization tool right into MacOS and docker is the one borrowing it to spin up a hidden Linux Machine inside your system.

Windows has its own trick called WSL (Windows Subsystem for Linux), originally built so people could use Linux without leaving Windows. Docker very conveniently uses it to run its Linux machine.

Docker is very smart about this, it just borrows these pre-built functionalities from each OS and becomes your saviour for running containers conveniently.

So what is happening exactly?

You must have got an idea that whats really happening is your OS spinning up a hidden Linux System somewhere and using Linux native features to run your containers flawlessly. So everything Docker appears to do on your Mac or Windows machine is actually happening inside a tiny Linux VM running in the background.
Docker isn’t running your Ubuntu or Debian, it has made its own bare bone Linux using LinuxKit, which focuses on being fast and not carrying anything it doesn’t need.

Why does my docker feels slow sometimes?

As discussed docker is using a Virtual Machine so file syncing between your original machine and this VM takes time, this is the reason why bind mounts can lag and take time, that's also why Docker Desktop lets you configure how much CPU and RAM its Linux VM can use. You've been feeding a tiny computer inside your computer this whole time and never knew it.

Much done talking, here’s some real task you can do right now

You got to know about Namespaces, now its time to test that concept in an actual system, go to your terminal and type:

docker run -it --rm alpine sh

You'll see an alpine Linux container spin up, with its terminal open. Now type:

ps aux

This lists all the processes along with their process IDs, what do you see? You might see something like this:

See that your shell is PID 1. Numero Uno! And inside this container, it genuinely is, that's not a trick, the kernel really did hand it PID 1. What it doesn't know is that this is PID 1 of its own tiny world, not your whole system. It is stuck in the classroom (the container) we created, with no idea the rest of the school (your system's real processes) even exists. That's Namespace doing its job.

Type exit when you are done poking around with this terminal. Now type this in your terminal:

ps aux

Yes again! now you can see a whole list of processes going on with its own PID(Process ID’s). You can see all of the Processes happening on your system, Chrome chugging your RAM, the Spotify tab that is kept idle or the VS Code window that you have opened all can be tracked from here.

Wrap-up

Next time you run docker run in your Mac/Windows, just remember about your friendly Linux VM buddy that is doing all the heavy lifting of your containers and clever Docker, who makes use of Mac and Windows features and acts as an abstraction not letting you know about the freeloading of features it does under the hood!

Further Reading: If you enjoyed this, I will highly recommend How Containers Work! by Julia Evans. It's one of the best resources for understanding the containerization process in an easy and concise way.