Docker for Dummies

iamprovidence
10 min readNov 9, 2022

--

In a cloud, that’s all they talk about, the Docker ☁️☁️☁️ Docker here, Docker there, Docker is everywhere. Even thought you are not DevOps-engineer, it is a must to know what Docker is, otherwise prepare to be disgraced.

If you are interested in what the hell Docker is. How it is different from Virtual Machine. And how simple C# application can be containerized, just stay with me to the very end and I promise all those questions will be covered. Are you ready? If so, let’s get straight to the point.

Why Docker?

How often have you heard or even said yourself something like that:

It works on my machine 😖

That exactly what Docker try to solve. It runs not only your application but also everything that it require to be executed, stuff like libraries, packages, scripts and so on. This way, if you manage to run your application in Docker, it is safe to say everybody can run your application.

What is Docker?

The concept of Docker is similar to Virtual Machines. Both of them can be used to run applications in isolation from each other. With the popularity of microservices, Docker have emerged as an excellent alternative to Virtual Machines. Why is so? To answer that question, you need to understand the difference between those. Look at the images below, and make sure you understand everything.

We will start from the bottom to the top. A virtual machine is an emulation of a computer that runs on your machine. To run it, you need a host, the computer that is running the VM. Next you need a software that create and runs a VM, it's called a hypervisor. And you can see at the very top we have three applications, with each own dependencies, binaries files and libraries, and each of them running in a separate VM with its own operating system (OS).

Now move to the right image. You already can tell it is much simpler. Docker Daemon, is like hypervisor, a software to run Docker. With Docker, your applications can share the host’s OS. Means, the program running in Docker are as “native” as the actual native processes running on the host.

To run your program in a virtual machine, you need to configure VM itself, a separate OS for it, assign a static resource limit and so on. VMs are slower and not as lightweight as containers. On the other hand, with Docker, you spend less resources and time to achieve the same level of isolation.

So if VM is an emulation of a computer, Docker is an emulation of a process, after all that is all it does, runs processes.

How Docker?

Before starting with Docker, we need to have a small program to run. Simple console application fits our needs.

You can see, it does not do much. Just prints a value, increases it by one, and waits a second to continue counting.

Before going Docker it is important to learn how to run your program as a process. So, how do you run that program? I mean, you pretty much get use to pressing “Run” button in VisualStudio. But how would we start an application without VisualStudio help? This can be achieved with good old СLI. Open your console, and run each command one by one:

And voilà, we are done. Our program successfully running.

At the very end, you can see, I have pressed ctrl + C to stop and exit that program.

Now, when we know how to run our application just with console commands, we are pretty much good to start with Docker. For that, we need to create a file with .Dockerfile extension and fill it with next content:

What the hell we just did? 🤔

We created a Dockerfile, a file with sets of instruction needed to run an application with Docker.

Let’s investigate that file line by line:

  • FROM — a valid Dockerfile should start with FROM instruction. It sets base Image. Do not think about it much for now, I will explain what Image is later. You saw that with our program to make it run we needed to install DotNet SDK, otherwise our dotnet commands won’t be available. The same is true here. Before starting, we need to install SDK, and that is exactly what FROM does.
  • COPYCOPY instruction copy all the content from source folder on your computer to destination folder in your Docker. In our case, we are copying our application to Docker, into app folder.
  • WORKDIR — with WORKDIR we set working directory for all future instructions. If such directory does not exist, it will be created. Or in other words, it just changes a directory inside Docker.
  • RUN RUN executes command inside Docker
  • CMD CMD configure a command which will be run every time we start an application in Docker. It also could be done in a variety of other ways CMD [“dotnet”, “run”, “ — no-build”], ENTRYPOINT [“dotnet”, “run”, “ — no-build”]

Alright, we have described to Docker how to run our application 😏. Before moving further, take your time and investigate differences and similarity between what we did manually to run the application and Dockerfile.

So, let’s continue. The same way our program required build before running, we also need to build that Dockerfile. This can be done with next command:

The result of executing that command will be an Image with counter-image name. The Image is a template for running your program, it contains the source code, libraries and dependencies to run your app. Long story short, it is just build artifacts of your program.

You can verify that it successfully build with next command:

Something similar should appear:

The next step would be to build a Container. A Container is a process that runs your program. To do that, use next command, that creates a new container based on an image:

The container is created, but it is not running yet. To get a list of existing containers (processes) run:

This will show you something similar to:

Now, it is time to finally start our container:

We need--interactive flag to see output of a program in a console.

As you can see, the program works the same way as it used to:

To see a list of only active containers, use next command:

Pat yourself on the shoulder, you did a great work. You have learned how to start your program with Docker 😃

Next step would be to clean up everything 😒

Stop your container:

Remove it:

And now let’s remove the Image:

And that's it 😃 We learned how to run our application without VisualStudio. We also taught Docker how to do it. After that, we built an image and container based on it. And then we run that container and clean everything up. What a journey! 😤

A list of commonly used commands:

Here are some commands that I found useful to run with PowerShell:

Some may not like using commands, and prefer a graphical interface. For those, you can try Docker Desktop. However, sometimes CLI is necessity, especially if you are running in CI pipeline.

It was a first part of this article. Further, we will discuss docker-compose. Do a little break and go drink a coffee ☕️

docker-compose

Let us make our program a lit of bit more complicated.

Currently, every time when we run our application, it starts counting from 0. What if we want to continue counting from the place we stopped. For that, we need to persist each value in some storage, for example in Redis.

Let’s update our code slightly:

Now we can run our program with Docker CLI as we did before, run Redis with Docker, configure network between those two and so on. However, there is a better way. We can use docker-compose instead.

A docker-compose is a tool to manage multiple containers. To start with it, add docker-compose.yml file with next content:

So what we have here:

  • version: defines language specification for compose file. Different versions support different instructions.
  • services: this sections describe containers configuration.
  • my-counter/my-redis: custom container names. Starts a section with configuration for our container
  • build: tells Docker which folder should be used to locate Dockerfile. It will build an Image from that file or used already build one.
  • image: tells the Docker which image to use. Redis image is not available on our computer, so for the first time it will be pulled from the Internet and stored locally

Do you remember all those steps we did with Docker to start our application? You don’t need that with compose. Just one command:

Images are built, images are pulled, containers started… and we got an error 😕. Our application can not connect to Redis… That is quite common issue, that one container can not access another.

If you read a little documentation, you can find something like this:

By default Compose sets up a single network for your app.
Each container for a service joins the default network and is both reachable by other containers on that network, and discoverable by them at a hostname identical to the container name.

Meaning that you should use my-redis instead of localhost to target across containers.

Let’s stop our containers…

And rewrite yml file. But this time we are passing the correct connection string via environment variable:

…and we can start our compose again.

This time everything works as expected 😏

However, we also could do it in a different way:

network_mode: host gives the container raw access to host’s network. This is especially useful when you want to access an application on your PC from the container.

Let’s investigate one last docker-compose.yml file with some common instructions that we have not seen yet:

So, what is new here:

  • ports: the list of ports that will be mapped from the container to the outside world. First value describe available port on your PC, while the second one in your container. This is especially useful when you want to access the container from an application on your PC.
  • depends_on: express dependency between services. Affect priority in which services are started. It does not make one container wait for another to be ready! Somehow people always confused by this one 😒
  • restart: define in which circumstances container should be restarted no|always|on-failure|unless-stopped. By default it is no.
  • container_name: sets container name to be used instead of the one used in services
  • volumes: where we map the log and data from the container to our local folder. This allows us to view the files directly in their local folder structure instead of having to connect to the container. Again, the first value in your PC, the second one in a container.

Full specification of all commands can be found here.

Notice, how respectless I was about conventions. In one place, ports are described with double quotes, while in another they are completely ignored. Do not be like me 😬. It was done, just to show different syntax. When writing real configuration, be consistent in styling.

Back to our example forone last time 😃. We store values in Redis, but even a simple text file would work. Try yourself to extend that example. This time make it so our application and Docker use the same source with values. Try using volumes for this. When you are done, you can verify your solution with mine.

And that was the end of the second part😃. I can tell you are already sleeping😴. Go drink another coffee ☕️ I hope you don’t have heart issues 💔

DockerHub

The last thing you need to know is DockerHub.

Similarly to the way we share code with GitHub we can share Images with DockerHub.

You already seen us using publicly available images like Redis and DotNet SDK. If you want to share your own image with other, it can be achieved with next steps:

  1. Create an account in DockerHub
  2. Login to that account

3. Push your image

And that’s it!✨ Now anybody with a computer 💻 and Internet 🌐 can download your program and run it locally.

You can also download any image you like from DockerHub with next command:

That was a long story, let us sum up and finish with it😃

Summary

That was a big one 😪 But you made it🎇 My congratulations.

Remember, we are not DevOps engineer here, only software developers and honestly, if you are able to understand everything that is going on here you pretty much can call yourself an advanced docker user.

We covered all the important topics, like Dockerfile, Docker CLI, docker-compose and DockerHub. Those are a good start, and in secret you are unlikely would need more😉.

Don’t forget to give this article a clap if you enjoyed it 👏

You can support me with link below ☕️

And follow me to receive more about Docker ✅

--

--

iamprovidence

👨🏼‍💻 Full Stack Dev writing about software architecture, patterns and other programming stuff https://www.buymeacoffee.com/iamprovidence