Docker: The Container Metaphor with Profound Revolution
Many regard containers as a virtualization technology. They are missing out. Docker has much more to offer. It is a graceful solution to some of the most painful experience in development and deployment we have ever had. Just imagine the all-too-common case where before the pending deployment the Dev has to ask Ops to upgrade in production one of the binary dependencies to release x.y.z or to supply new environment variables and command-line flags to enable some new features.
This stinks on so many levels. Besides the velocity loss and communication overhead, it is just so easy for the application to accidentally rely on artifacts from the previous release or outdated environment variables we forget to unset. If the upgrade aborts, rolling back all the (not atomic, not idempotent) steps will be just as painful and error-prone. Isn’t it wonderful if developers could install all the dependencies themselves, implement the application, ship exactly what they have just built, and deploy it in an atomic, hermetic unit? It is such a simple idea with immense repercussion. Docker is the state-of-the-art implementation of this idea.
Hermetic Encapsulation of Application, Configuration and Dependency
Product shipping includes not just the application but also its configuration (environment variables, flags, etc) and external dependencies (Debian packages, vender of Golang, node_modules of node.js, etc.). These are packaged into a single Docker image. A Dockerfile outlines the steps of how a Docker image is built. By defining all the configuration and installing all dependencies through Dockerfile, the output Docker image is self-contained with all we need to run what we ship. It offers hermetic deployment because the container abstraction provides simple isolation boundary that ensures that the environment configuration does not escape to host or to other containers (sandbox) and it cannot be altered by either the host OS or other containers without explicit authorization (lockbox). When the container is brought down, the entire environment is brought down with the application. Nothing in the environment lives longer than the application itself, a simple yet powerful abstraction.
The Container Metaphor: Universal Tooling regardless of Language and OS Distro
Docker produces a single artifact from each build. No matter what language or framework the application is implemented in, no matter what distribution of the Linux the host runs, the output is always multi-layered Docker image, which is built and handled by the Docker tooling.
This is the shipping container metaphor. A single, transferable unit that universal tooling can handle, regardless of what it contains. Like a container port, any Docker tooling (such as an image registry that stores and transport images like a host git repository would for codebase) has to ever deal with one kind of package – the Docker images. This is powerful in two ways. It means all the Docker tooling you or others make will be reusable to everyone else. It also means the build artifacts are extremely portable, which can be deployed on any system with a Docker daemon running (profound impact in the world of hybrid cloud).
As presented in the example at the beginning, upgrades in the system are often performed in a non-atomic, multi-step operation – download, patch, coalesce. Is it possible to simply pull down a new (light) release image, deploy it, and if things go south, quickly roll back to the previous image we were using? Docker does exactly this, because a Docker image encapsulates all the patches and configuration. Docker can instantiate an instance using the new image just pulled and if everything passes, do an atomic rename.
Fast, Ephemeral and Stateless Instances
A container is just a process that talks to the Linux kernel directly. It takes seconds to start and run a Docker container, while it takes minutes at best for a virtual machine. Virtual machines are long-lived in nature, as they aim to simulate actual hardware and runs guest OS that might be different from that of the host. Containers, on the other hand, share the Linux kernel and the images from which they instantiate are often just a few megabytes, while a VM image usually takes a few gigabytes. Such lightweight property means containers are perfect for stateless, ephemeral, elastically scaling systems.