The cover image for this post is by Erwan Hesry
At the time of writing, Docker had recently announced changes to their licensing agreement. But what is Docker and why does it exist? What are Containers? And are there any alternatives to Docker?
Docker and it’s container format solves the problem of running a web-based application and all of it’s dependencies on a server. The target application, any libraries that it requires, and an entire operating system are included in the container.
Containers are of images - not the types of images which represent photos or graphics - and an image has one or many layers. Each layer represents all of the file changes made to the image.
Containers are one way of creating scalable applications - allowing us to deal with lots of requests at the same time without having to build more servers, by starting Containers as our concurrent user count goes up, and stopping them as our concurrent user count goes down.
A Little Background
Have you ever had to report a bug, and heard a developer say:
"It works on MY machine"
This can be incredibly frustrating for everyone involved - including, believe it or not, the developer.
Most of us developers aren’t intentionally trying to be facetious when we say things like this. Most of us are trying to point out that there is a fundamental difference between running the application on our machines vs where you are running it.
note: most of this article was written with web-based applications in mind
With traditional web-based applications, a team of developers (or sometimes a solo developer) writes an application and have to install it on a web-server. A web-server is a computer which is connected to the Internet and offers the web-application up to anyone with authorisation to use it. Any website you’ve ever visited - from Netflix to Google - is hosted on a number of servers.
Before the DevOps movement, installing a web-application on a server required making changes to that server - just like when installing any application on your computer, like a web browser or a media player. Most of the time, this means installing dependencies on that server: let’s say that the web-application uses .NET, the specific version of .NET that is uses needs to be installed too. Not only that, but the server might have to have a specific version of an operating system (like Windows, MacOS, or a Linux distribution) installed on to it, before the application and it’s dependencies can be installed.
Here’s where it all gets a little wobbly, though: most web servers will have multiple web-based applications, and all of their dependencies installed on them. It is very rare to find a server with just one web-based application running on it.
Why is that a problem?
Imagine that we want to install two applications on a server:
- One which runs on Go 1.17, on a Linux distribution
- One which runs on Go 0.98, on a Linux distribution
You might not think that this is a big problem, but we have two separate dependencies, each at different versions:
- Go 1.17
- Go 0.98
these are all examples, and version numbers picked out of the ether, by the way
Installing different versions of Go might lead to incompatibilities, as the application which takes a dependency on Go 0.98 might not work with the later version (1.17). And if we’re unlucky enough that installing a later version means replacing the older version (i.e. they don’t run side-by-side), we could end up with an application which doesn’t even start.
again, these are made up examples which don’t necessarily reflect reality
How Do We Fix This?
Imagine a system where we could take an application, all of it’s dependencies, and the operating system that it runs on, and put it into a container, box, image, zip, or some other thing.
Containers (or more correctly OCI Containers) attempt to solve this issue by taking all of the things that an application requires (including it’s supporting libraries, and operating system) and have theme completely separate from everything else that could possibly run on a server.
This means that you can run lots of different containerised applications on a single machine, regardless of their individual dependencies, without having to worry about the dependencies of one application breaking the dependencies of any other application.
A container is a running instance of an Image - this is not the same thing as a photo or a graphic. An image, this context, is a read-only collection of all the files required to run the application.
Since an application which is running inside of a container has all of it’s dependencies with it, this nullifies the:
"It works on MY machine"
excuse, and means that debugging becomes simpler - since only the application has changed, not the dependencies or underlying operating system.
This also means that many different applications can be served by the same server, and we can also have scalable applications - we increase our ability to deal with lots of requests at the same time without having to build more servers, by starting Containers as our concurrent user count goes up, and stopping them as our concurrent user count goes down.
Docker is actually a series of applications which allows us to run applications which have been bundled into containers. It isn’t the only way to run containers, alternatives include:
Containers and Images?
A Container is a running instance of an Image, in the same way that a physical book is the printed and bound version of a manuscript.
An Image contains all of the file changes required to install the application and all of it’s dependencies. Using one of our applications from above
"One which runs on Go 1.17, on a Linux distribution"
Our image would be made up of:
- A distribution of Linux (usually Alpine Linux or Debian slim)
- An installation of GoLang version 1.17
- A copy of the built application
You can think of an image as a list of instructions for building and running the application and installing all of it’s dependencies.
Images are created by adding lines to a plain text file, describing how to build the container. Examples of these lines would be:
- The operating system required
- Steps for installing any dependencies
- Steps for obtaining the source code for the application
- Steps required to build and run the application
Each of the lines in an Image file creates a Layer. These Layers represent the files which have been created, updated, or deleted within the Image.
When an an Container is created as an instance of an Image, it simply runs through the instructions included in the image and boots the application.
This isn’t exactly correct, but it’s correct enough to get the point
And Docker allows us to run as many Containers as we would like, allowing a server (or even a desktop or laptop computer) to run as many applications with different dependencies as we want, at the same time.
Whilst Containers aren’t a requirement for building applications at scale (being able to increase our ability to deal with lots of requests at the same time without having to build more servers), it is one way of achieving it.
We have covered what OCI Containers are, how they related to Images, and how Containers can help us achieve scalable applications. We’ve also covered the fact that Docker is one (of many) ways of running Containers, but also that there are others like podman and containerd.