Previously in part 1, we covered containers at a high level, including how they scale, alternatives and properties. Now in this second post we find out what Docker is, how it runs on Linux, and how images work. And we’ll do all this without the usual analogies of containers and ships, but instead with restaurants & food. Sound good? Well, keep reading…
What is Docker?
Docker is the dominant container technology (as of 2018); with around 79% of organisations in a 2017 survey saying Docker is their primary method for running containers.
There is a tendency to conflate Docker with containers and assume they are one and the same. This is not true. While Docker is dominant among container deployments today, there are competitors (example: CoreOS Rocket) and other aspects to containers (example: container orchestration engines such as Kubernetes – more on this in another post).
Docker is confusingly can refer to different things. It can be Docker the open source community container project (now called Moby), Docker the company that supports and makes money from the said project, and also Docker the technology that manages and runs containers.
Three different meanings for Docker…
This blog post addresses Docker the technology. Specifically we discuss Docker on Linux. Sure, it does run on other OS, but for sake of brevity we’ll stick with Linux.
A brief bit of history (skip if you want)
Containers are not a new idea; the concept behind containers has been around since at least the year 2000 when FreeBSD introduced Jails. Sun introduced Zones to their Solaris operating system in 2004 and four years later LXC emerged as an open source container project for Linux.
Docker started life in 2013 as a project by Solomon Hykes in dotCloud, a platform hosting company. People were asking him how dotCloud ran their apps (answer: Linux Containers) but wanted “…that low level piece to do that magical thing…“
To meet this demand for a simple way to containerise apps on Linux, Solomon and some other engineers came up Docker as an open source technology and a company. It originally ran on LXC (part of Linux Containers), before moving to Docker’s own library called libcontainer in 2014, opening up support for other OS outside Linux.
A year later in 2015 Docker (the company) rewrote libcontainer into a new runtime called runC and donated it, along with container formats, to a new Linux Foundation project called the Open Container Initiative (OCI).
Docker has the concept of images, which are akin to items on a menu in a restaurant. You can order each item as many times as you want and a new plate of that particular meal will come out. Images specify what will run in the container (think item on the menu) and a container is a running instance of an image (think of the meal on a plate – can be multiple of the same meal).
Think of images as items on a menu and containers as plates of food…
Registries are centralised stores of container images. Building on the image is a meal on a menu analogy, registries can be thought of as supply companies that deliver ingredients to the restaurant (this analogy is a bit tenuous…).
What’s in a Docker Image?
An image is a binary that includes requirements for running the container and metadata on how to run it. The binary part contains the ‘ingredients’ for the meal. A Docker image specifies a stack of layers in a Dockerfile. The first layer is generally a parent image, which is altered by subsequent layers. The parent image may be a base image, which is where it isn’t itself built from other images. A base image is either built from a host OS user space, or from the special Docker image called scratch.
The ingredients in a container image recipe
That mention of user space is critical to understanding containers. More on this soon.
The previously mentioned Dockerfile specifies the base image and layers. Each command in the Dockerfile creates another layer.
Mapping a Dockerfile to image layers
Why does Dockerfile specify an OS?
Containers leverage the host kernel yet the base image specifies an OS (like Ubuntu). So what gives?
The base image is an OS user space atop the host kernel. For this reason it is not possible to run Windows containers atop a Linux container host or vice versa natively, due to different kernels. Containers are not VMs! When you run a container on a host with a different type of kernel, Docker runs up a VM under the covers and leverages its kernel.
The role of a base image and layers in a container
As Linux distros share the same kernel, there are multiple user space options available, from the small lightweight Alpine or BusyBox or scratch base images, all the way through to full-featured Ubuntu or CentOS. The latter have a much larger file size, so you may want to carefully consider your base image choice.
Okay, but what is a Layer?
There are some clever bits to the way images work. A layer is just the file differences from the layer below. This keeps file sizes for most layers, other than the base layer, reasonably small as they only specify changes to specific files.
The second clever bit is this – all layers other than the top one are immutable (read-only). This brings about these properties:
- Every container instance has its own personal top layer that is read-write for storing changes it has made in the filesystem
- Containers can share the read-only layers, minimising disk space requirements.
This layered design has the following advantages:
- Minimises size on disk
- Reduces image build time
- Makes it easy to patch multiple containers in a single place (more on this soon)
Patching, made easier…
Containers can make the never-ending patching task a lot easier.
Firstly, the container hosts can be patched separately to the container images running on them, following an appropriate testing methodology (of course).
Secondly, by using a base image that is regularly patched, containers can be rebuilt on the updated patched base image. This is a fundamental change from patching server OS and applications in place and has many benefits including test validation prior to migration to production and having a deployment that you are confident can be rebuilt.
The Bits of Docker
Docker containers on Linux combine native OS features, including cgroups (resource management) and namespaces (isolation of network, storage, compute etc.) to provide isolated environments to run applications. It is not necessary to understand all the under-the-cover bits to use containers, so skip this section if you want.
There are multiple bits to Docker:
The bits of Docker…
Here are the app components, starting from the top of the diagram:
Docker Client (docker)
Docker Server (dockerd)
The server component that provides container management services is a Docker product called dockerd. It has a REST API that provides access to the following types of function:
- Containers. List/Create/Inspect/Stop/Start/Export/Pause/Unpause/Attach etc.
- Images. List/Build/Inspect/Push/Remove/Search/Delete/Export/Import etc.
- Networks. List/Inspect/Create/Remove/Connect to/Disconnect from/Delete
- Volumes. List/Create/Inspect/Remove/Delete
- Exec. Run commands in containers
For a complete list see https://docs.docker.com/engine/api/latest
So, dockerd is the brains controlling the container hosting. It delegates tasks to other components as we shall see next. Together docker and dockerd are known as Docker Engine.
High-Level Runtime (containerd)
The high-level container runtime is containerd. It is critical but likes to keep a low profile. Think of containerd as the chef in the restaurant (returning to the restaurant analogy from earlier). The chef is important to your enjoyment of the meal, but you don’t need to talk to them in person. The waiter (Docker or Kubernetes) takes your order, and then containerd chef prepares the meals unseen in the kitchen.
Containerd abstracts underlying OS-specific features (using the low-level runtime runC) and handles image management. Images are either in Docker or OCI format. Containerd exposes a North-bound API (gRPC, not HTTP REST) that is consumed by dockerd.
Low-Level Runtime (runC)
If containerd is a chef, then runC is the kitchen hand who does the hard work preparing the ingredients. It is a low-level container runtime that makes the system calls required to configure and run containers. This low-level runtime functionality is specified by the Open Container Initiative (OCI).
RunC is a client wrapper around the libcontainer library, and requires an OCI Runtime filesystem bundle, which is an image unpacked onto the filesystem.
Various Linux kernel features underpin Docker (to stretch the restaurant analogy to breaking point, think of the kernel features as appliances in the restaurant kitchen). Features include:
Control groups (cgroups) is used by Docker to control resource (i.e. CPU, memory, storage) utilisation per container.
Namespaces limit what resources the process in the container can see. The container perceives that it is in running in an OS with processes, network, mounts etc. but it is actually only seeing an isolated (partitioned) set of resources. Namespaces can apply to:
- Processes (pid)
- Networks (net)
…and other parts of Linux too.
Maybe this topic deserves a blog post of its own.
Wrapping it all up
Hopefully now you have a better idea, thanks to use of a tenuous restaurant analogy, about what Docker is, how it is put together and how images work.
In the next blog post, we’ll dive into how to install Docker and run containerised apps.
 Docker is introducing VM techniques to allow this
 See this blog post for more on image sizes: https://www.brianchristner.io/docker-image-base-os-size-comparison/
 See this blog for good explanation on container runtime types: https://www.ianlewis.org/en/container-runtimes-part-1-introduction-container-r
 For OCI Image Format, see: https://github.com/opencontainers/image-spec/blob/master/spec.md
 For OCI Runtime Spec, see: https://github.com/opencontainers/runtime-spec/blob/master/runtime.md