Introduction to Docker for Java Developers
Introduction
Let’s talk about Docker, a technology that was born in 2013, made a huge impact in the software industry, and saw a rapid adoption. First of all, what is Docker? From the official documentation, it can be seen that Docker has different meanings in different contexts. For now, let’s limit ourselves to the following description: Docker is an engine that orchestrates containers. A container is a sort of VM. A sort of, because a container isolates different processes on a machine, rather that virtualizing a new environment. In the Linux kernel, there are 2 features: cgroups and namespaces that allow processes to be isolated. Applications running in different containers are isolated from each other (to a degree: can share ports, if running on same physical machine of course an CPU-intensive app from a container can impact an app running in a different container). This is a reason why we can say a container is a sort of virtual machine. More explanations about containers can be found here.
Now, that we understand what’s a container, we can understand why Docker gained so much popularity: because the way it can build, ship, and run applications. The official documentation describes an image like an executable file. An container is the running instance of an image. Just like more than one process can run the same executable image, more than one container can run a image. A image describes how to create a container. A Docker image is like a source file. One can create a Docker image by adding to an existing Docker image. From a Docker image that runs the Java runtime, we can create a new image that adds a .jar file to that initial image running the Java runtime only. Now, comes the question, how do we share the images? The answer is given by Docker hub, a place where Docker images can be found. A Docker hub is like a Maven repository. The oficial Docker hub can be found here. Everybody can add an image to the Docker hub. Companies, or other parties interested, can add a private image to this hub. Here can be found a list of Docker-specific terms explained in Java-specific terms.
Now, we understand how Docker builds, ships, and runs applications: an application is build and encapsulated in an image; then, the image is put on Docker Hub; someone else can download the image from the hub and run it (start an container).
Running Java applications
Now, that we have a grasp of what Docker is, let’s run some containers. The first example will be easy, just to get us started. Assuming you already installed Docker, you can create somewhere on your disk a new folder, create there a file named Dockerfile
, and add the following content:
The file named Dockerfile
describes a Docker image. To actually create the Docker image, with the name helloworld
, run:
On the first run, just like Maven, a Docker image with name ubuntu
and version tag latest
(just like in Maven, where you can alter the Java source code to get a slightly different jar file, and specify a version for the resulted executable, in Docker you can modify a little Dockerfile and obtain a different image; it’s more or less a convention, most recent image of an application has the tag latest) will be downloaded locally. To see the locally available images, run docker image ls
. You can now run the newly created helloworld
image, by running:
Now, let’s play with the tags. Modify the file named Dockerfile, we no longer use ubuntu
as a base image, but busybox
. Now, run
Inspect the output of docker image ls
. Now, run this version 2 of helloworld image:
More samples can be found here.
Now, let’s run a Java application. Let’s create a simple Spring Boot application, and run it in a Docker container. For this, go to start.spring.io, from the list of dependencies select Web, give a meaningful Group and Artifact, and generate the project. Open the project in your favorite IDE, and add a controller like this:
Now, let’s build the project:
To run the project in Docker, first we need to create an image; so, let’s add a file named Dockerfile, describing how to create an image running our sample web application. So, in the root folder (where pom.xml is) add a file named Dockerfile with the following content:
We inherit from an image named openjdk, the official image running Java. We copy the generated jar file into the image, and run the file. Notice a pattern: we compile the project on local machine, and run it in Docker. We don’t compile the project in Docker, because we would need a Docker image containing JDK as well, not just JRE. Our image size would increase, unnecessary. Now, let’s create the image:
then run it:
In this docker run
command, we see 2 additional parameters:
-it
, we need the container to run in interactive mode-p 8080:8080
, we need to map the container’s 8080 port to the local machine’s 8080 port.
Now, just go to http://localhost:8080/hello, and see that everything is running as planned. Here you can see a Github repo with this application.
An multiple dependencies app
This was a rather simple app. Let’s now move to an more real-life scenario. We would need at least a database as dependency, not to mention other services like caching, message queues, and so on. For now, suppose we need a database. We could run the database in an container, the application in an container, and everything would work fine. First, let’s see how we would run Postgres DB in an container:
In last command, we are mapping Postgres 5432 port to local machine’s 5432 port. We also define the user (POSTGRES_USER=postgres
), password(POSTGRES_PASSWORD=password
), and database name (POSTGRES_DB=name
) we want Postgres to run. We also give the container a name, --name postgress-name-example
.
Now, we can extend our simple Spring Boot application, to store names in a database.
First, add the following to pom.xml, under dependencies:
Then, add the following entity:
and the following repository:
You can, now, add the following controller:
And, to connect to Postgres DB runing in the container, add the following to application.properties file:
Now, you can build the Spring Boot application, and run it on local machine while DB is running in a container. But, we want to run the application in an container as well. We want to expose the 5432 port from DB’s container to application’s container. Also, the DB and the application are related; we want them to start both and stop both, like a unit. How do we achieve this? Meet services, and a new file called docker-compose.yml
. In the root folder, add the file docker-compose.yml with the following content:
Next, we also need to change the application.properties file. Change the following line: spring.datasource.url=jdbc:postgresql://mypostgres:5432/name
. Next, you need to install docker-compose. Rebuild the docker-sample-hello image, now everything is set to run:
Now, we are running the Java application and the DB in a Docker container, each in it’s own container. To see the benefits of our approach, suppose we need to record the number of times our CRUD links are accessed. We will need an other service, Redis. Redis is an open-source, in-memory data structure store. We will use this solution to store and update the statistics. To include a Redis container, we only have to add the following lines at the end of docker-compose.yml:
Now, we will add to application.properties:
Also, don’t forget about pom.xml:
Now, Spring Boot magic created an bean of type RedisTemplate<String, String>
. As side note, a bean of type RedisTemplate<String, Integer>
will not be magically created (at least, at the time of writing). So, now, we can add a controller, to view the statistics:
After the controller for saving and retrieving names is also adjusted, we can rebuild the project, the Docker image. After we access several resources, we can access http://localhost:8080/statistics and see something like:
A working example can be found here.
Conclusions
For development, Docker brings a huge advantage: we can test technologies, like Redis, Postgres, MongoDB, without installing anything on our machines. When working in a team, we can create a Docker environment, so every newcomer will no longer have to spend a lot of time installing everything. When it comes to running Docker in production, there is a longer discussion. In a cloud environment, Docker can be an advantage; but, as always, you should look at the big picture.
Some useful links
https://jaxenter.com/nobody-puts-java-container-139373.html
http://blog.codepipes.com/containers/docker-for-java-big-picture.html
https://en.wikipedia.org/wiki/Docker_(software)