Not so long ago I had to do research on how to run a dotnet application on Docker. Thanks to that I’ve learned the basics of Docker. The text below is based on the notes that I’ve written down in the process.
Docker quickstart series
- Docker quickstart – installation
- Docker quickstart – running containers – standard, interactive, daemon modes
What is Docker
Docker is an application packaging platform and runtime. Each image (Docker’s name for application package) consists of layers containing application dependencies and the application itself. Layers may contain system, runtime, libraries, etc. What’s important is that each layer is readonly. Thanks to packaging one may start several separated instances of one image on one physical machine.
How it differs from a virtual machine
Taking the above into account one could say that Docker is in fact a virtual machine. Well, things are getting a bit complicated there.
Docker is not a virtual machine yet it virtualizes some resources. For example, each container has an isolated file system or network (although it may be changed). On the other hand, each process has its hook on the host machine. Just take a look at “ps” output:
badamiak@dockerhost:~$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES badamiak@dockerhost:~$ ps -e | tail -n 20 | head -n 17 843 ? 00:00:00 exim4 844 ? 00:00:01 apache2 912 ? 00:00:01 docker-containe 995 ? 00:00:00 sshd 997 ? 00:00:00 systemd 998 ? 00:00:00 (sd-pam) 1000 ? 00:00:02 sshd 1001 pts/0 00:00:00 bash 1375 ? 00:00:08 apache2 1376 ? 00:00:08 apache2 2569 ? 00:00:00 kworker/u4:1 17839 ? 00:00:00 kworker/0:2 18882 ? 00:00:04 kworker/1:0 18940 ? 00:00:00 kworker/0:0 19197 ? 00:00:00 kworker/1:1 19224 ? 00:00:00 kworker/1:2 19382 ? 00:00:00 kworker/0:1 badamiak@dockerhost:~$ docker start testapp11i1 testapp11i1 badamiak@dockerhost:~$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9d1f02fbd5bf badamiak/testapp:latest "/bin/sh -c 'dotnet r" 27 hours ago Up 11 seconds 0.0.0.0:8080->8080/tcp testapp11i1 badamiak@dockerhost:~$ ps -e | tail -n 20 | head -n 17 998 ? 00:00:00 (sd-pam) 1000 ? 00:00:02 sshd 1001 pts/0 00:00:00 bash 1375 ? 00:00:08 apache2 1376 ? 00:00:08 apache2 2569 ? 00:00:00 kworker/u4:1 17839 ? 00:00:00 kworker/0:2 18882 ? 00:00:04 kworker/1:0 18940 ? 00:00:00 kworker/0:0 19197 ? 00:00:00 kworker/1:1 19224 ? 00:00:00 kworker/1:2 19382 ? 00:00:00 kworker/0:1 19490 ? 00:00:00 exe 19495 ? 00:00:00 docker-containe 19510 ? 00:00:00 sh 19522 ? 00:00:00 dotnet 19535 ? 00:00:00 dotnet
As you can see, starting Docker container (instance of image) host has started several new processes. I am talking about PIDs 19495, 19510, 19522 and 19535. The container that I’ve started is a dotnet core web host. That’s the reason why there are running dotnet processes. As I don’t have dotnet installed on host, it proves that the container processes are delegated to Docker’s host.
When you consider the above, the difference between Docker and a virtual machine is getting clearer. It is that VMs are virtualizing whole hardware whilst Docker exposes “bare-metal” devices as they are seen by the host machine.
Another difference is that setting up a VM requires you to store whole stack used by your application. I mean that VMs have to store a lot more data than containers. VMs are using virtual storage devices that contain bootloader, operating system, drivers, libraries and last but not least your hosted application. If you want to scale an application horizontally you have to clone a complete virtual drive image. Docker architecture limits the amount of data that has to be stored for a container. By providing readonly layers it makes it possible to reuse the image, so several containers may run using single image. Atop of all readonly layers Docker inserts a read/write persistence layer. That persistence together with a container config (network, volumes, environment vars, etc.) is what differs the containers from each other. See the console output below to notice that running ubuntu under Docker consumed about 130MB for image, then starting new instance adds under 150KB to the disk usage. The reason is that both containers are using one image. The only stored information is the difference between the image and the container. It works a bit like snapshots in VMs but with Docker you can run several snapshots simultaneously.
badamiak@dockerhost:~$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE badamiak@dockerhost:~$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES badamiak@dockerhost:~$ df / Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda1 7736784 2429116 4891620 34% / badamiak@dockerhost:~$ docker pull ubuntu Using default tag: latest latest: Pulling from library/ubuntu f069f1d21059: Pull complete ecbeec5633cf: Pull complete ea6f18256d63: Pull complete 54bde7b02897: Pull complete Digest: sha256:bbfd93a02a8487edb60f20316ebc966ddc7aa123c2e609185450b96971020097 Status: Downloaded newer image for ubuntu:latest badamiak@dockerhost:~$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu latest 0f192147631d 8 days ago 132.8 MB badamiak@dockerhost:~$ df / Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda1 7736784 2595552 4725184 36% / badamiak@dockerhost:~$ docker run --name instance1 ubuntu badamiak@dockerhost:~$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ec6458250c9b ubuntu "/bin/bash" 3 seconds ago Exited (0) Less than a second ago instance1 badamiak@dockerhost:~$ df / Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda1 7736784 2595672 4725064 36% / badamiak@dockerhost:~$ docker run --name instance2 ubuntu badamiak@dockerhost:~$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2a1a5090e8e5 ubuntu "/bin/bash" 1 seconds ago Exited (0) Less than a second ago instance2 ec6458250c9b ubuntu "/bin/bash" 4 seconds ago Exited (0) 1 seconds ago instance1 badamiak@dockerhost:~$ df / Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda1 7736784 2595812 4724924 36% /
Installing Docker Engine
There are two ways to install Docker.
The easy way using shell script provided by Docker inc.
The hard way if you don’t trust third party shell scripts. The hard way can be found at Docker page.
While learning Docker I’ve used the easy way so I will describe this one here. Using the easy method you don’t have to care about any dependencies or setting up the system (except for installing it of course). As I am no Linux geek, I’ve decided to use the easy way.
Basically all you have to do to install Docker is to call:
wget -qO- get.docker.com | sh
What it does is:
- “wget -qO- get.docker.com” downloads the script from ‘get.docker.com’. The ‘-q’ flag stands for quiet, and means that ‘wget’ won’t write progress to standard output (stdout). The ‘-O’ flag redirects downloaded content to the specified file. By using ‘-O–‘ we basically say that ‘wget’ should write the output to stdout which I use in the next step.
- By using the pipe sign I redirect the output of the ‘left command’ to the ‘right command’s standard input stream (sdtin).
- ‘sh’ processes data from stdin. Due to the fact that I’ve redirected the ‘wget’ stdout to the ‘sh’, the ‘sh’ command executed the downloaded script.
After the script processing ends you may call ‘docker -v’ to verify installation.
badamiak@dockerhost:~$ docker -v Docker version 1.11.2, build b9f10c9
After installation you may want to try to run your first container. Docker provides an official image for Hello-World application. All you have to do is to call ‘docker run hello-world’. If everything is working right you will see the line ‘Hello from Docker!’ in the result.
badamiak@dockerhost:~$ docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world c04b14da8d14: Pull complete Digest: sha256:0256e8a36e2070f7bf2d0b0763dbabdd67798512411de4cdcf9431a1feb60fd9 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker Hub account: https://hub.docker.com For more examples and ideas, visit: https://docs.docker.com/engine/userguide/ badamiak@dockerhost:~$
After reading through this post you should be able to run your own instance of Docker and also a sample application contained in the hello-world image.
I hope that I’ve also explained the basic concepts behind Docker and its architecture.
In the next part of this tutorial I will cover running a parametrized container, setting up virtual networks and attaching host directories as volumes to a container.
If you have any questions or remarks please write a comment and I shall soon address your concerns.