Docker — (not) the Ultimate Tutorial
Docker Tutorial with small SpringBoot + Gradle + RabbitMQ + PostgreSQL example.

If you just want a simple project to base yours on as how to integrate all or some of these technologies, I would recommend that you go right to this GitHub Repository. If you don’t know nothing or very little about Docker, this article is for you. It’s objective is to bootstrap your knowledge of Docker from none to “kinda good I think”. Maybe even with some interesting insights along the way. I will begin with some reframing so that the Docker concepts can slide right in so please, hold on to my hand and let’s begin our journey.
A looooooong time ago…

The first programmable computers were gigantic mainframes. These metal monsters had one interesting characteristic between them: The hardware executed only one program at a time. Programs were executed in sequence, and you needed to wait for the last one to finish it’s execution so that you could load and run the next one.
In the (not so) good ol’days

Somewhere along the way, someone noticed how that procedure was inneficient. So an idea came along: How about we create one general program that keeps executing all the time, and every other program executes inside it? So the Operating System was born.
(Yeah. It just needed someone to have a very vague idea like that for the creation of Operating Systems.)
As Personal Computers emerged, programming became more accessible. Unfortunately a lot of time was still wasted reinventing the wheel, so the patterns makefile and library emerged.
(Nobody officially recognize Makefile and Library as patterns. But that is what I identify them as being.)
Some definitions just so we are on exactly the same page: Makefile being a file with a series of commands that build your code into an application (creating folders, configuration files, setting environment variables and such). Library being a piece of code that knows very well how to do something, and that can be appended to your code so that you don’t have to learn how to do that — You just want results after all. Who wants to learn new things?
Some time ago, in a world like ours, but not really

Some time passed and came along things like Internet and Maven. Maven sped up the development time a lot because it would facilitate for you to write the building logic (makefile) and it would also automagically download and provide for your application any library it needed. It was truly something beautiful.
(I know, I know. My knowledge and precision about computer and software development history doesn’t cease to deliver.)
And now (finally) to what this article is about
Please, raise a hand everyone that has sometime asked for help or helped someone to configure a development environment. Maybe you’ve even read (or written) a doc somewhere with a step-by-step of how to install and configure a dozen different applications (svn/git, ide, database, client to query the database, webserver, etc.) so that new developers can begin their work somewhat fast. Maven and Gradle help by bundling the building instructions and how to get the necessary libraries together with the project. But how should this application interact with other applications? And how should these other applications be configured?
Another problem is that when you have an application running directly inside the Operating System (remember, program running inside program), very little can be garanteed. Applications can interfere with each other in so many ways that problems can hardly be foreseen. Can you foresee that Tenacious D’s Wonderboy playing on your Spotify App will stop right at the climax just because Windows Defender decided to eat up 99% of your harddisk’s bandwith in the middle of an thursday afternoon? Godamnyou Bill Gaaaates!!!
(disclaimer: Bill Gates seems like an awesome dude trying to eradicate AIDS, give poor countries basic sanitation and a lot more. But it is really easier to blame him for Windows’s faults :P )
Welcome to Containerization

Succintly, think of a container as being an Operating System minus everything that is not absolutely necessary to run a specific application. By doing that, the application is totally isolated from other possible applications running on the same machine. You can know exactly what files it is using and in what structure they are, how it interacts with the external world (outside the container), and that nobody is messing with those environment variables. You can even define how much of an specific resource it can use (like RAM or CPU). Basically you can decouple what is running, from where it is running. The Operating System only knows it is running containers, and how much resources it is giving them. The applications inside containers don’t even know they are inside containers.
Docker
Docker is a containerization framework. In Docker lingo, an image is a static thing that specifies how to run a specific container. A container, is an image while running. You could think that an image is like a class, and a container is like an instance of an object defined by that class.
Docker Hub is a website with docker images. With Maven+MavenCentral (and now Gradle) you can specify how to build your application and what libraries it will use, and it automagically downloads everything. Now with Docker, you can specify what applications your solution will need, and what configurations they will use, and it automatically will download and execute them for you (maybe even building your own application if you need it). Isn’t it beautiful?
Let’s dig in
We will be using my GitHub example. You can follow it’s readme (which is very much more succint) or you can follow through here, where I will help you along the way.
First of all, please install Docker. This tutorial will assume you are using an unix system.
With the installation finalized, let me show you the power of docker. Write the following command:
docker pull postgresThis command downloaded from Docker Hub an image of the latest version of PostgreSQL. You can see every downloaded image you have by executing:
docker image lsIf you access the Docker Hub Postgres’s page down there it will inform you what container’s environment variables the application (Postgres) will use, and what they mean in it’s configuration.
Let’s run this image
docker run --name mypostgres -t \
-e "POSTGRES_USER=postgres" \
-e "POSTGRES_PASSWORD=postgres" \
-d postgres:latestArguments:
- -e define an environment variable inside the container
- — name defines a name to the running container
- -d runs the container in detached mode, meaning that it will run in the background and release the terminal
- postgres:latest is the image we are running
With the following command, you can see which containers you are running:
docker psAnd that is all. You have a database running in your computer already. Pure IT magic. “But how do I access it?” — you ask. Clever question my imaginary friend.
We could connect to the Postgres database inside the container through the terminal, but this is sooo old school. Let’s do something more interesting. Execute the following command:
docker run --name mypgadmin -t \
-e "PGADMIN_DEFAULT_EMAIL=tyler.durden@gmail.com" \
-e "PGADMIN_DEFAULT_PASSWORD=postgres" \
-p 81:80 \
-d dpage/pgadmin4This is almost the same command from before, with an extra argument in the format -p <hostPort>:<containerPort>. With that, we are saying to the container to connect it’s port 80, to the port 81 on our machine.
When you run this command, you will also notice that it will automagically download the image dpage/pgadmin4 and run it for you. After all is done, open a browser, and go to localhost:81. Welcome to your containerazed Pgadmin. No installation, no fuss. Go ahead and login with the email and password you used as parameters.
“Ok, so wait. We are running a database inside one container, and Pgadmin in another container. They are isolated from one another. How do I add on Pgadmin a connection to the database on the other container?”
Right on point again imaginary friend! Every container has it’s own local IP. Execute the following
docker inspect mypostgresIt will spew a gigantic json with everything that is going on in this specific container. Just do a
docker inspect mypostgres | grep "IPAddress"to find which IP it is using. You can now go to Pgadmin on the browser, click new server, add the IP, and “postgres” as user and password, as defined when we executed the run command on the PostgreSQL image.
You must be thinking how much work it would be to setup a few containers, connected with one another using these commands. Well, if you are not thinking that you should, because it really would be a lot of work. For more complex cases, there is a tool called Docker Compose. Docker Compose comes bundled with Docker, so no worries — it surely is not like you didn’t even install Docker and is just reading without following the instructions at all.
Download this GitHub project. This is a very barebones project, composed of a SpringBoot application (Producer) that posts messages on a RabbitMQ queue each 5 secs, and another SpringBoot application (Consumer) that reads from the same queue, saves the messagens on it’s database, then sleeps for 30 secs.
.
├── docker-compose.yml
├── tutorial_docker_consumer
| ├── src
| ├── Dockerfile
| ├── build.gradle
├── tutorial_docker_producer
| ├── src
| ├── Dockerfile
| ├── build.gradle The Consumer and the Producer both have a Dockerfile. Dockerfile is a file that specifies how to build an docker image. In this case, both dockerfiles just copy a .jar from the host (our machine) to inside the container, and execute it with the jdk8.
In this example, we are using a Gradle’s Docker plugin so that Gradle builds the docker image for us.
Execute the command below in both folders “./tutorial_docker_consumer” and “./tutorial_docker_producer”:
gradle build dockerVerify that the images “rabbithole/tutorial_docker_consumer” and “rabbithole/tutorial_docker_producer” were created
docker image lsGo to the project’s base folder and execute
docker-compose upAnd that’s all folks! Docker will (download if necessary and) run a Postgres container, a RabbitMQ container, a Pgadmin container (if you feel like checking the database), a Consumer container and a Producer container. All of that is configured in the “./docker-compose.yml” file. Watch the terminal a little. You will see all the containers booting up, until the prints from the producer being consumed (slowly) by the consumer.
In this imaginary scenario, Producer capacity is too high for only one Consumer. For this scenario too, there would be no problems in having 3,4 or even a dozen Consumers, consuming in parallel from the queue and saving in the same database. So let’s do that.
Scaling up
For scaling, the cool kids are using Kubernetes. It is robust, powerful and awesome. And we will not be touching it :)
For this beginners tutorial we will use Docker Swarm. Just because it is simpler to use, it is already bundled with Docker, and anything more would be outside the scope of this tutorial (and honestly, because Swarm sounds much more awesome than Kubernetes).
Before anything, you do this
docker swarm initThis command will initialize Swarm Mode, and will set your machine as the manager (the leader of the pack). A swarm is just a bunch of machines running a bunch of containers. The Swarm Manager (the machine where “swarm init” was executed) is the one machine that says what every other machine in the swarm (Swarm Workers) should execute.
(If you have access to other machines you could make them join in as workers, but I will not show you how to do that :) )
Now let’s run the same service as before. Go to the project folder and execute
docker stack deploy -c docker-compose.yml myswarm- docker stack deploy means you will execute your service in the swarm
- -c points to the docker-compose.yml file that specifies your service
- myswarm the name you are giving to this specific service in the swarm
It is almost the same as we did before. The difference is that now the service is running in the background, it can be scaled, and you could use multiple machines as processing nodes for your service. This means that the configuration “replicas: 4” in the consumer service in the docker-compose.yml will be used now. There are 4 instances of the consumer service running in parallel now.
To see what is happening with the consumer, execute the command:
docker service logs -f myswarm_consumerOpen another terminal, and execute the command below to see what is happening with the producer:
docker service logs -f myswarm_producerYou will see in the logs that the consumers alternate consuming from the queue. “Ok, but isn’t it a lot of work still? Do I have to keep guessing how much replicas of each service I need? And how do I limit the resources being consumed by each container? And what about monitoring? And metrics? And logs???”. Wow! Calm down dude. Let’s explore these questions in another article in the future. Maybe even using Kubernetes. Or maybe not. Who knows?