What is Docker?
Docker is a technology that allows you to create and run containers for your applications. Containers are isolated environments that run an application and include its dependencies. They are typically minimal, including just what you need to get your app running and nothing else.
Containers are not meant to store permanent data. It is expected that your container might be destroyed and recreated at any time, so any data saved within the container itself will be lost. It should just contain the binaries and code needed for your application to run, with any permanent data stored externally, for example on mounted volumes or a cloud database.
There are a number of advantages of using containers to develop and deploy your application:
-
No human error: You have a predictable environment each time you deploy. Instead of having your system admin run commands via SSH on a virtual server, you now have a documented script in your source code of what is deployed to your container.
-
Reproducible environments: You can run a system that is very close to production on your development machine. This makes finding bugs a lot easier. It is much less likely that a bug is introduced because someone forgot to run a command in production. Or as the old excuse goes: "It worked on my machine."
-
Infrastructure management tools: You can take advantage of tools and technologies that handle the provisioning, networking, load balancing and scaling of containers such as Docker Swarm or Kubernetes.
-
Dependency management: If you have two services that need different versions of Python, for example, then you can run both of these services on the same virtual machine but in different containers and their dependencies won't interfere with each other.
-
New team members: Companies typically have a giant, incorrect, and incomplete document of how to set up a developer's machine which frustrates the hell out of new team members. Containers can help you improve this. Your development environment can be set up entirely with a bash script.
Finally, it is worth mentioning that although Docker is almost synonymous with containers, there are other container technologies out there such as rkt. We'll focus on Docker in this article, but it's good to know there are other choices.
Docker is also a company that sells Enterprise solutions for containers. However the Docker engine and Docker desktop products are free to use. Docker engine uses containerd, which is open source.
Installing Docker
You can install Docker Desktop on Windows and Mac. This comes with a bunch of useful tools, including Docker Compose:
You can install Docker Server on Linux. And you can install other tools separately as well, such as Docker Compose:
Creating an Image Based on Alpine Linux
A Docker image is a binary file that contains the files needed to initiate and run a Docker container. Docker images are built using a series of commands in a file named Dockerfile
. These commands set up the image, and tell Docker what command to execute when the container starts up.
Alpine Linux is a lightweight Linux distribution that is popular for containers because of it's small size and minimal baggage. Small distributions are popular because they reduce the image build time and they provide more precise control over the dependencies that you install.
Here is an example of a Dockerfile, which is as simple as it could possibly get:
FROM alpine:3.9
CMD echo "hello world"
The first command uses the alpine:3.9
Docker image as a basis. It will grab the image for this and then run the rest of the commands against it. By default, Docker will get these images from the Docker registry, but that can be changed if required.
The second command is the command to run to start the container. Here we are just running echo
so this will output Hello World
to the console. The container will only stay alive as long as this command is running, so in this case it will immediately exit and shut down the container.
For containers that need to run continually, the final command would typically launch a process that never stops, such as a web server or database engine.
Running a Container from your Image
Run the following command in the same directory as the Dockerfile to build the container image, and tag it with the tag myimage
:
$ docker build -t myimage .
The result of running this is something like this:
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM alpine:3.9
3.9: Pulling from library/alpine
bdf0201b3a05: Pull complete
Digest: sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913
Status: Downloaded newer image for alpine:3.9
---> cdf98d1859c1
Step 2/2 : CMD echo "hello world"
---> Running in db62be8c00ca
Removing intermediate container db62be8c00ca
---> d351cb9614ec
Successfully built d351cb9614ec
Successfully tagged myimage:latest
Then, using the tag as a reference we can run the container:
$ docker run -t myimage
The result:
hello world
Docker files can do a lot more. For example, they can copy files from the file system into the image, and switch base images so that a different image is used as the base for running the container. We'll see an example of these features in the next section.
Use Case: Run Open Source Project with a Dockerfile
It is increasingly common nowadays for an open source project to expose a Dockerfile to make it easy to get set up and see the application running. It can assist newcomers to run the project without spending time installing myriad dependencies on their computer.
If you write open source software you might consider exposing a Dockerfile the project has a number of dependencies. For a simple Node.js project it may not be worth it, but for something running Node, Ruby, and MySQL you might do this to make life a bit easier for people who don't already have those installed.
In this example, we are going to take an open source application written in Microsoft's .NET Core framework, and run that in Docker. The application is an example made especially for Docker and can be found at the following link:
https://github.com/dotnet/dotnet-docker-samples/tree/master/aspnetapp
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
The first step is to clone this repository so that we have it locally:
$ git clone https://github.com/dotnet/dotnet-docker-samples
If you haven't used Git before, you can install it from here. It is not important that you have much knowledge of git as we are only using it to download the source code.
Once cloned, open the dotnet-docker-samples/aspnetapp
folder, and have a look at the files, and notice there is a Dockerfile. Which at the time of writing looks like this:
FROM microsoft/aspnetcore-build:2.0 AS build-env
WORKDIR /app
# copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore
# copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o out
# build runtime image
FROM microsoft/aspnetcore:2.0
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "aspnetapp.dll"]
This Dockerfile is based on an image from Microsoft for building ASP.NET applications and proceeds to issue commands to copy across the files and build them. It then switches images to one from Microsoft for running an ASP.NET application, proceeds to copy the built files to the current directory, and then runs the ASP.NET runtime against them.
If you have never used ASP.NET before you will soon be able to say that you built, installed and ran an ASP.NET application. All you would have done is typed a couple of Docker commands!
To build the image run the following commands from the command line:
$ cd dotnet-docker-samples/aspnetapp
$ docker build -t aspnetapp .
$ docker run -it --rm -p 8000:80 aspnetapp
This will build the image and then start a container using that image, mapping port 8000 on your computer to port 80 inside the image. You can now visit http://localhost:8000 to view this application running.
You should see something like this:
Docker Compose
A problem you will quickly encounter with running a single container is that it is not easy to set up a container to do a bunch of different things, such as process PHP files, host a MySQL database, and act as a web server using NGINX.
It is also unnecessary because containers are so lightweight (as opposed to Virtual Machines), which means there is no problem having a bunch of them running on the same machine doing different things.
The advantage of multiple containers and images is that it gives you the chance to split them across different virtual machines or physical machines and even create multiple containers from the same image across machines to handle additional application load.
The challenge with multiple containers is how to set them up so they know how to communicate with one another across the network, with appropriate security, and how to connect storage to those containers. Luckily there are a number of different options for solving this problem of "orchestrating" containers.
Perhaps the easiest way to get started is with Docker Compose.
Docker Compose uses a file which describes what components are needed to get a system running. This can include containers, files systems, network ports to be exposed, and so on.
Here is an example docker-compose.yml
file that describes such a setup:
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/code
- logvolume01:/var/log
links:
- redis
redis:
image: redis
volumes:
logvolume01: {}
In the Docker Compose file shown above, we run two containers. One is built from the local folder, and the other uses the Redis image. There is information about what volume to mount, for example the current folder is mapped to /code
inside the web container, and the folder logvolume01
is mapped to /var/log
inside the container.
The command docker-compose up
is used to start the containers and mount the volumes and get everything you have described in the docker-compose.yml
file up and running. You can use this for both development and production environments.
Kubernetes
Docker Compose is a great tool to get started on gluing containers together, but it is limited in functionality for production environments and is limited to running on a single host. You will need a different technology to run across multiple machines and handle fault tolerance, monitoring, and scaling.
Kubernetes is an open source container architecture. It orchestrates containers and has a vast array of features, including dynamic container creation, scaling, fault tolerance, probing containers for signs of life, and load balancing. The name comes from the Greek κυβερνήτης meaning helmsman. It is sometimes abbreviated as K8s, although I am not sure why as it isn't a very long name to begin with.
Kubernetes can be installed directly on virtual or physical machines. Alternatively, the major cloud providers including Google, AWS, Azure, and Digital Ocean all provide managed Kubernetes services which will simplify getting a cluster up and running.
To become familiar with Kubernetes requires some studying. It won't make much sense if you just hack around without some ground knowledge. There is a basic free edX course on this, which I have used to help me get started on this at work.
Kubernetes is not the only technology for orchestrating containers: there is also Docker Swarm, Vagrant, Monad, and OpenShift and many more. I feel that Kubernetes offers a 'safe bet' in terms of support in the future as of 2019. All of the major cloud providers support it, and recently Microsoft announced they are pulling their own container service out of the mix, in favor of Kubernetes.
Conclusion
Docker can make your life more convenient as a software developer. It can be used to run different environments predictably on your developer machine. It is also used in production, usually with an orchestration system like Kubernetes, to provide predictable deployments of your applications to servers. If you haven't tried Docker yet, I encourage you to do so.