
A developer at codeandcake.dev spent some time building a container runtime for fun and wrote up what it taught them about Docker. The post is short and better than most of the container explainers I have read, because it starts from the thing that never quite lands when you read documentation: a container is not a small virtual machine. It is a process on your kernel, with restrictions bolted onto it.
Your container is in /proc, right now
That is the first thing the post gets right. The kernel is still running the container process. If you know where to look, the process is listed in /proc like anything else on the box. There is no hypervisor, no second kernel, no emulated hardware. The isolation is real but it is isolation of view and resources, not a wall.
And there is no Linux feature called a container. As the post puts it, a container is really five kernel features in a trenchcoat. Namespaces decide what the process can see. Cgroups decide how much CPU and memory it can use. Networking is its own pile of machinery that most of us, the author included, do not fully understand. Images turn out to be folders that Docker stores somewhere and then hands to chroot or pivot_root, which makes that folder the new root directory for the process. When you type docker run with an Ubuntu image and a bash shell, that is the trick. A folder, a root switch, and a set of restrictions applied before your shell ever starts.
I have been running Docker for years and I still find that deflating in a useful way. The magic is all in the plumbing, and the plumbing is inspectable.
Go was a strange choice for runc
The most interesting part of the writeup is about language choice. Docker's container runtime, runc, is written in Go. Go's runtime is natively multi-threaded, which is a genuinely good feature for servers and an awkward one for a container runtime. A lot of the syscalls a runtime has to make need a single-threaded environment. So runc ships a C module that runs before Go's runtime initializes, purely to make those calls.
Then there is fork. Go does not have it in the way a runtime wants it, because fork only forks the main thread. A container runtime really wants to fork, apply a pile of restrictions to the child, and then exec the process you asked for. runc works around this by exec'ing /proc/self/exe with a special command line argument that marks it as a child process, which approximates a fork closely enough to work.
If you have ever wondered why container tooling feels like it is fighting the platform in places, this is why. It is fighting the language runtime, not the kernel.
Root is the part nobody has finished solving
The Linux kernel APIs a runtime needs either require root or used to, so the Docker daemon historically runs as root. That is a large amount of privilege sitting in one place, and the post says plainly that container security must be a very tough job. It is.
Rootless runtimes have improved things. The exception is networking. To give you the same features without root, a rootless runtime appears to have to reimplement the networking stack in userspace. That is a serious amount of work to get back to where you started, and it explains a lot about why rootless setups behave differently when something goes wrong with the network.
None of this is an argument against containers. It is an argument for knowing which of the five features is responsible for the guarantee you think you have. On my own boxes the honest answer, before reading this, would have been "Docker does it". That is not an answer. It is a shrug with extra steps.
The post ends where a lot of good technical writing ends, with the author still early in their own runtime and planning to keep going. Reading it made me want to poke at /proc on a running container and see the process sitting there. It will be there. It is always there.