An Ultimate Beginner’s Guide to Docker

Docker is the most popular tool for containerization and is widely used. Docker is the most important must-have tool in your toolkit that every developer or DevOps or even security engineer must learn. It provides a way of running your apps in isolated environments on a host just like a virtual machine would run but in a much faster and effective way. This article is an ultimate beginner’s guide to docker where we will learn how to install and start using it for your projects. Before learning to install it and use it we will also learn some of the internals of docker to better understand it as a tool.

What is Docker?

Docker is a containerization tool that containerizes your applications by building your code into packages called images and then deploying them to run as containers.

Figure 1.1

So docker is a tool or software that you will install in your local machine where you write code (optionally), on build servers where you turn your code into deployable artifacts called docker images, and on application servers where your run your code.

As shown in Figure 1.1, docker build command will pack your code into a deployable artifact called Image and docker run command will run the image as a container. These are the only two commands you will need to run your code as docker container on your local machine or laptop.

Ideally, for production or real-life projects, you would also need to store your created images in Docker Image repository (public or private) called Docker Registry using docker push command on the build servers and then pull them on your application servers using command docker pull and then run them as containers. The flow of docker looks like as shown in Figure 1.2.

Figure 1.2

One example of a docker registry is Docker’s Official Registry which is a public repository. You will find a lot of useful official docker images here which you will need to use as the base image for creating your own images. We will look into creating docker images in the next section.

How to create Docker Image?

We learned before that code in the code repository is built into a docker image. Now to pack the code into a docker image, docker build command is not enough. We need to add a file called Dockerfile at the root of the repository.

# Base Image
FROM python:3.6.12

# A Lablel  
LABEL author="iDL Tutorials"

COPY requirements.txt /tmp
RUN pip install --no-cache-dir -r /tmp/requirements.txt

COPY ./ /
WORKDIR /app

EXPOSE 8090
#Default command for docker run
CMD [ "python", "./main.py" ]

Dockerfile is a set of instructions docker needs to follow to create your image. Above, we see a simple Dockerfile. As you can see, it has various steps.

The first step is “Base Image” which is fetched from Dockerhub as it is an official public python 3 image from Dockerhub. This image comes pre-installed with python3 on a Unix OS. Now, this is just like an OS installed with no other applications on it. So we add our code and requirements in subsequent steps. These are pretty self-explanatory. The last step CMD is what command the image will execute when it is run using docker run.

On the left side, all CAPS words are called instructions and the right side is called arguments. You can get details of all instructions from the official Dockerfile reference page: https://docs.docker.com/engine/reference/builder/

Now when you run the command, docker build you get a new image that now also packages your application and on executing docker run, will run your code in an isolated environment and will be exposed on port 8090.

docker build -t myimage .
docker run --name my1stcontainer -p 8090:8090 myimage

That’s it. That’s how you run your code in a container.

What is Docker container?

Before we start installing and using Docker, let’s quickly explore about docker container as well.

The docker engine that we use to build and run docker containers is installed as software and it runs Docker containers.

The Docker container under the hood is an LXC Linux container. Linux containers were introduced much earlier than docker and hence containers are not a new concept. Docker uses Linux features to create these Linux containers. Docker with its rich functionality makes it very easy for a developer to run applications using containers that otherwise can be created only by advanced Linux users.

For Microsoft users, this does not mean you cannot run docker on Windows. Docker installation on Windows comes with some way of Linux virtualization and hence works on Windows as well. This type of docker installation is called Hyper-V containerization.

Microsoft has done a lot of work in this area and has created Windows containers as well which mimics Linux LXC containers and allow you to use Windows base images on Windows. So basically there are two types of containers you can run in Windows:

  1. Hyper-V containers – Linux containers can run on Windows and they actually run in a very small embedded Linux VM. This works on concept that docker can run inside a VM.
  2. Windows Server containers (WSC) or Process Containers – Created by Windows in 2016, can only run on Windows and do not need to run inside VM. But there are limitations with Windows Containers. If you want to run a WSC on Server 2016, you must have built the container from a Server 2016 base. If you run it on Windows Server 1709 it will not work: it is blocked from starting. This also means, for example, if you run Nano Server as your host OS, you can only run Nano Server base images on that host. This is simply not a limitation that you’ll find with Linux containers.

If you work on .NET then Microsoft provides .NET base images at https://hub.docker.com/_/microsoft-dotnet which can run on Linux servers as well. This is recommended way of running .NET images as you don’t have to pay for an OS License for the host OS and also the base images are lighter and faster. It is much better to run containers on Linux hosts than the above two methods on Windows hosts.

Difference between Virtual Machine and Docker Container

The below is a summary of the difference between Virtual Machine and Docker Container.

Virtual Machine
Docker container

Virtual MachineDocker Container
Uses hardware-level virtualizationUses OS-level virtualization
Has its own OS and shares hardwareShares OS kernel and hardware
Virtual Image = APP + App Dependencies+ Full OSDocker Image = APP + App Dependencies+ OS without kernel
VMs are heavy and slow as OS needs to boot upContainers are lightweight and fast. Docker container OS does not need to boot up as base images pack OS without a kernel and Docker container runs as a Linux process -:)
VMS can run any flavor OS on HostDocker containers can run only OS which has the same kernel as Host OS. For example, cannot run windows containers on Linux OS.
VMS cannot run inside a Docker containerDocker containers can run inside a VM

As you read above, the BASE IMAGE that we use in Dockerfile is only has an application layer and hence does not need to boot as the container will use HOST OS. Hence docker is really fast. This is also the reason you can also build a docker image without any base OS image using “scratch”

FROM scratch

The “scratch” keyword is mostly used to create base images. But you can also have your application run using “scratch” by installing all the dependencies. So you will have a really small image as compared to the docker image with a base OS image as it will come with a lot of packages installed.

Installation of docker

Now since you understand a lot about docker. Now start with installing and using it on your local PC or laptop.

You can install Docker on a Mac or Windows and Linux. You can visit https://docs.docker.com/get-docker/ and follow the very detailed steps for installation according to your host OS. When you visit the given link you will be given the option to select your OS flavor and then you can easily install Docker on your local. Once it is done, come back to this tutorial where learn how to start using docker.

Hello World Docker container

Now once docker is installed in your machine. Let’s quickly create a docker container on your local using the docker run command. Since we are not using any Dockerfile so it will just pull the image from a Docker registry and start the base image for you and then run the command that you are passing which is again optional.

$ docker run ubuntu /bin/echo 'Hello world'

The output of the command above will look like below:

Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
7b1a6ab2e44d: Pull complete 
Digest: sha256:626ffe58f6e7566e00254b638eb7e0f3b11d4da9675088f4781a50ae288f3322
Status: Downloaded newer image for ubuntu:latest
Hello world

As shown above, it pulls the image if not present (in this case from Docker Hub)and then starts it, and then runs the command passed to it and exits. It will use the base image as is and run the container unlike running an image built from Dockerfile where you add more layers to the base image to suit your application needs.

Docker Registry

A Docker registry is a system for versioning, storing, and distributing Docker images. This is from where docker will pull or download the image if it is not already presented locally. And this is where you will push or save your newly created images as well to be used on machines other than yours. You cannot copy and save your image and transfer your images like a file from one machine to another. You will push your image to a Docker Registry and pull it on another machine.

The most popular Docker Registry is the DockerHub which is a hosted registry used by default when installing the Docker engine, but there are other hosted registries available for public use such as AWS, Microsoft’s own registry, and Google’s own registries.

For a real-life project, you will need a private registry as you do not want people to access your private images which have your application code. You can use Docker Hub, AWS, Google and Microsoft, and a few other providers to create your private registry only accessible to you. You can also create your own private registry on your local or some server using the official Docker Registry image and play with it for learning purposes. The below is how it is started:

$ docker run -d -p 5000:5000 --restart always --name registry registry:2

Once started you can pull an image from the public Docker Hub Registry and then push it to your private registry as follows:

$ docker pull ubuntu
$ docker tag ubuntu localhost:5000/ubuntu
$ docker push localhost:5000/ubuntu

Now we can use our local registry image to run the hello world program:

$ docker run localhost:5000/ubuntu /bin/echo 'Hello world'

In a Dockerfile the FROM will look like below:

FROM localhost:5000/ubuntu

Docker commands to run docker containers

Till now we have learned a great deal about docker concepts and have also installed docker. Now let’s learn about docker commands that you will be using on a daily basis while working on docker.

In this section, we will revisit commands that we have already used in the above sections and learn a bit more about them.

Docker build command

Docker build command is used to build or create images for your applications. In microservices-based applications, you may have tens to hundreds of small application code repositories and hence will need tens or hundreds of docker images. This is because the docker image is your deployment artifact. The build image command is as follows:

docker build -t <your_username>/my-private-repo:<version> .

In the above, the dot at end of the command refers to the location of the Dockerfile. You will generally run this command from the root of your application code and will also have Dockerfile at the same location, otherwise, give the path of Dockefile.

If you do not wish to push your images to a registry and only want to learn docker you can just simply use below:

docker build -t <image-name>:<version> .

The above is a typical build command that you will use to build your image. This command if run without version (example, 1.0 or 1.1 or 2.0 and so on) will create an image as the latest tag. If run without tag.

$ docker build .
$ docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
<none>                  <none>              fd69c90c69a9        13 seconds ago      927MB

Now if we build using tag and no version

$ docker images
REPOSITORY              TAG                 IMAGE ID            CREATED              SIZE
myimage1                latest              fd69c90c69a9        About a minute ago   927MB

Now on using version

$ docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
myimage1                1.0                 fd69c90c69a9        6 minutes ago       927MB
myimage1                latest              fd69c90c69a9        6 minutes ago       927MB
$ docker build  -t idltutorials/flask-demo:1.0 .
$ docker images
REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
idltutorials/flask-demo   1.0                 fd69c90c69a9        10 minutes ago      927MB
myimage1                  1.0                 fd69c90c69a9        10 minutes ago      927MB
myimage1                  latest              fd69c90c69a9        10 minutes ago      927MB
$ docker run idltutorials/flask-demo:1.0
 * Serving Flask app "main" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on all addresses.
   WARNING: This is a development server. Do not use it in a production deployment.
 * Running on http://172.17.0.2:8090/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 672-821-496
$ curl http://localhost:8090
curl: (7) Failed to connect to localhost port 8090: Connection refused
$ docker ps
CONTAINER ID        IMAGE                         COMMAND              CREATED              STATUS              PORTS               NAMES
6dd9b1fbbbde        idltutorials/flask-demo:1.0   "python ./main.py"   About a minute ago   Up About a minute   8090/tcp            unruffled_proskuriakova
$ docker run -p 8090:8090 idltutorials/flask-demo:1.0
$ docker ps
CONTAINER ID        IMAGE                         COMMAND              CREATED             STATUS              PORTS                    NAMES
01cdb4ea4087        idltutorials/flask-demo:1.0   "python ./main.py"   9 seconds ago       Up 8 seconds        0.0.0.0:8090->8090/tcp   trusting_tesla
$ curl http://localhost:8090
Hello World from Flask
$ docker run -p 8090:8090 idltutorials/flask-demo:1.0
 * Serving Flask app "main" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on all addresses.
   WARNING: This is a development server. Do not use it in a production deployment.
 * Running on http://172.17.0.2:8090/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 872-355-518
172.17.0.1 - - [14/Nov/2021 22:25:16] "GET / HTTP/1.1" 200 -
$ docker run -d -p 8090:8090 idltutorials/flask-demo:1.0
9dba6f82fd93009cce92a913b64f9c1c4c1e34edc66a8f5525ac8b240117ea95
$ docker ps
CONTAINER ID        IMAGE                         COMMAND              CREATED             STATUS              PORTS                    NAMES
9dba6f82fd93        idltutorials/flask-demo:1.0   "python ./main.py"   24 seconds ago      Up 23 seconds       0.0.0.0:8090->8090/tcp   compassionate_bhaskara
$ curl http://localhost:8090
Hello World from Flask
$ docker run -d --name demo-container1 -p 8091:8090 idltutorials/flask-demo:1.0
16adc5ed58e326dd99a41b27fd51dcdd3b98df8098c4ed5b7fb9dc640d8b5e92
$ docker ps
CONTAINER ID        IMAGE                         COMMAND              CREATED             STATUS              PORTS                    NAMES
16adc5ed58e3        idltutorials/flask-demo:1.0   "python ./main.py"   20 seconds ago      Up 19 seconds       0.0.0.0:8091->8090/tcp   demo-container1
9dba6f82fd93        idltutorials/flask-demo:1.0   "python ./main.py"   3 minutes ago       Up 3 minutes        0.0.0.0:8090->8090/tcp   compassionate_bhaskara
$ curl http://localhost:8091
Hello World from Flask

Docker ps command

$ docker ps
CONTAINER ID        IMAGE                         COMMAND              CREATED             STATUS              PORTS                    NAMES
16adc5ed58e3        idltutorials/flask-demo:1.0   "python ./main.py"   20 seconds ago      Up 19 seconds       0.0.0.0:8091->8090/tcp   demo-container1
9dba6f82fd93        idltutorials/flask-demo:1.0   "python ./main.py"   3 minutes ago       Up 3 minutes        0.0.0.0:8090->8090/tcp   compassionate_bhaskara
$ docker ps -a
CONTAINER ID        IMAGE                         COMMAND                  CREATED              STATUS                      PORTS                    NAMES
16adc5ed58e3        idltutorials/flask-demo:1.0   "python ./main.py"       About a minute ago   Up About a minute           0.0.0.0:8091->8090/tcp   demo-container1
9dba6f82fd93        idltutorials/flask-demo:1.0   "python ./main.py"       5 minutes ago        Up 5 minutes                0.0.0.0:8090->8090/tcp   compassionate_bhaskara
01cdb4ea4087        idltutorials/flask-demo:1.0   "python ./main.py"       8 minutes ago        Exited (0) 5 minutes ago                             trusting_tesla
6dd9b1fbbbde        idltutorials/flask-demo:1.0   "python ./main.py"       9 minutes ago        Exited (0) 8 minutes ago                             unruffled_proskuriakova

Docker logs

$ docker logs 16adc5ed58e3 -f
 * Serving Flask app "main" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on all addresses.
   WARNING: This is a development server. Do not use it in a production deployment.
 * Running on http://172.17.0.3:8090/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 749-993-607
172.17.0.1 - - [14/Nov/2021 22:31:36] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [14/Nov/2021 22:34:10] "GET / HTTP/1.1" 200 -

Docker stop container

$ docker ps
CONTAINER ID        IMAGE                         COMMAND              CREATED             STATUS              PORTS                    NAMES
16adc5ed58e3        idltutorials/flask-demo:1.0   "python ./main.py"   4 minutes ago       Up 3 minutes        0.0.0.0:8091->8090/tcp   demo-container1
9dba6f82fd93        idltutorials/flask-demo:1.0   "python ./main.py"   7 minutes ago       Up 7 minutes        0.0.0.0:8090->8090/tcp   compassionate_bhaskara
$ docker container stop 9dba6f82fd93
9dba6f82fd93


$ docker ps
CONTAINER ID        IMAGE                         COMMAND              CREATED             STATUS              PORTS                    NAMES
16adc5ed58e3        idltutorials/flask-demo:1.0   "python ./main.py"   4 minutes ago       Up 4 minutes        0.0.0.0:8091->8090/tcp   demo-container1
$ curl http://localhost:8090
curl: (7) Failed to connect to localhost port 8090: Connection refused
$ docker ps -a
CONTAINER ID        IMAGE                         COMMAND                  CREATED             STATUS                          PORTS                    NAMES
16adc5ed58e3        idltutorials/flask-demo:1.0   "python ./main.py"       5 minutes ago       Up 5 minutes                    0.0.0.0:8091->8090/tcp   demo-container1
9dba6f82fd93        idltutorials/flask-demo:1.0   "python ./main.py"       8 minutes ago       Exited (0) About a minute ago                            compassionate_bhaskara
...
$ docker stop 16adc5ed58e3
16adc5ed58e3
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
$ docker ps -a
CONTAINER ID        IMAGE                         COMMAND                  CREATED             STATUS                      PORTS               NAMES
16adc5ed58e3        idltutorials/flask-demo:1.0   "python ./main.py"       7 minutes ago       Exited (0) 48 seconds ago                       demo-container1
9dba6f82fd93        idltutorials/flask-demo:1.0   "python ./main.py"       11 minutes ago      Exited (0) 3 minutes ago                        compassionate_bhaskara

Docker restart container

$ docker container restart  9dba6f82fd93
9dba6f82fd93
$ docker ps
CONTAINER ID        IMAGE                         COMMAND              CREATED             STATUS              PORTS                    NAMES
9dba6f82fd93        idltutorials/flask-demo:1.0   "python ./main.py"   13 minutes ago      Up 16 seconds       0.0.0.0:8090->8090/tcp   compassionate_bhaskara
$ docker restart 16adc5ed58e3
16adc5ed58e3
$ docker ps
CONTAINER ID        IMAGE                         COMMAND              CREATED             STATUS              PORTS                    NAMES
16adc5ed58e3        idltutorials/flask-demo:1.0   "python ./main.py"   11 minutes ago      Up 7 seconds        0.0.0.0:8091->8090/tcp   demo-container1
9dba6f82fd93        idltutorials/flask-demo:1.0   "python ./main.py"   14 minutes ago      Up 47 seconds       0.0.0.0:8090->8090/tcp   compassionate_bhaskara

Docker remove container (To recreate a container)

$ docker stop 16adc5ed58e3
16adc5ed58e3
$ docker run -d --name demo-container1 -p 8091:8090 idltutorials/flask-demo:1.0
docker: Error response from daemon: Conflict. The container name "/demo-container1" is already in use by container "16adc5ed58e326dd99a41b27fd51dcdd3b98df8098c4ed5b7fb9dc640d8b5e92". You have to remove (or rename) that container to be able to reuse that name.
See 'docker run --help'.
$ docker container rm 16adc5ed58e3
16adc5ed58e3
$ docker ps -a
CONTAINER ID        IMAGE                         COMMAND                  CREATED             STATUS                      PORTS                    NAMES
9dba6f82fd93        idltutorials/flask-demo:1.0   "python ./main.py"       17 minutes ago      Up 4 minutes                0.0.0.0:8090->8090/tcp   compassionate_bhaskara
01cdb4ea4087        idltutorials/flask-demo:1.0   "python ./main.py"       20 minutes ago      Exited (0) 17 minutes ago                            trusting_tesla
6dd9b1fbbbde        idltutorials/flask-demo:1.0   "python ./main.py"       22 minutes ago      Exited (0) 21 minutes ago                            unruffled_proskuriakova
6807f6fc2435        idltutorials/flask-demo:1.0   "-p 8090:8090"           23 minutes ago      Created                     8090/tcp                 jolly_napier
6a149016a0ee        idltutorials/flask-demo:1.0   "python ./main.py"       25 minutes ago      Exited (0) 23 minutes ago                            elegant_hypatia
$ docker run -d --name demo-container1 -p 8091:8090 idltutorials/flask-demo:1.0
d514e30258d0429ebe37f5b7636c8dda99f5bc7f999bee617477fe82fbe86133
$ docker ps 
CONTAINER ID        IMAGE                         COMMAND              CREATED             STATUS              PORTS                    NAMES
d514e30258d0        idltutorials/flask-demo:1.0   "python ./main.py"   22 seconds ago      Up 20 seconds       0.0.0.0:8091->8090/tcp   demo-container1
9dba6f82fd93        idltutorials/flask-demo:1.0   "python ./main.py"   19 minutes ago      Up 5 minutes        0.0.0.0:8090->8090/tcp   compassionate_bhaskara

Docker list command

$ docker container ps
CONTAINER ID        IMAGE                         COMMAND              CREATED             STATUS              PORTS                    NAMES
d514e30258d0        idltutorials/flask-demo:1.0   "python ./main.py"   43 seconds ago      Up 42 seconds       0.0.0.0:8091->8090/tcp   demo-container1
9dba6f82fd93        idltutorials/flask-demo:1.0   "python ./main.py"   19 minutes ago      Up 5 minutes        0.0.0.0:8090->8090/tcp   compassionate_bhaskara
$ docker container ls
CONTAINER ID        IMAGE                         COMMAND              CREATED             STATUS              PORTS                    NAMES
d514e30258d0        idltutorials/flask-demo:1.0   "python ./main.py"   56 seconds ago      Up 55 seconds       0.0.0.0:8091->8090/tcp   demo-container1
9dba6f82fd93        idltutorials/flask-demo:1.0   "python ./main.py"   19 minutes ago      Up 5 minutes        0.0.0.0:8090->8090/tcp   compassionate_bhaskara
$ docker ls
docker: 'ls' is not a docker command.

Docker commands for troubleshooting

In order to troubleshoot the docker containers. You may use below commands:

Get container information such as, container-id and check the node status if it is Up

$ docker ps -a

Retrieve the logs for a container

$ docker logs <container-id>

Use the command below to stop the failed container, if it is still running

$ docker stop <container-id>

Delete the stopped container

$ docker rm <container-id>

Conclusion

I hope you got fair bit of Idea on what docker is and how it works. If you are very beginner this article marks the beginning of your Docker Journey… Go and explore more!! Best of luck!!

Similar Posts