Since Docker is really omnipresent in the last few months for very good reasons, I want to pick this up for this blog post. I will show how to Dockerize a CUBA-Platform app and run it as a container together with the corresponding database. If you are not familiar with the basics of Docker, I will also scratch the surface of this technology a little bit for you.
The Docker 10,000 Feet Overview
Docker is a tool that arose circa 2013. Since that time it pretty much whirls the industry of IT. It lets you isolate your applications in a way that allows each application to define their own environment without interfering other applications. Just think of it like virtual machines that you might know from VMWare. But, instead of emulating the whole hardware with an abstraction layer, this emulation layer goes away.
This brings some fundamental speed improvements as well as some restrictions. You can create a (Docker-) Container within a few seconds instead of an hypervisor-based VM that will launch within a few minutes. On the other hand, you can currently only create Docker containers on a host machine that’s running Linux, because the containers share the kernel. Actually, it’s not virtualization at all, it’s more a clever way of namespacing your OS. Additionally, the idea is not new. Where hypervisor-based virtualization (at least in the x86 world) is about 15 years old, the beginnings of paravirtualization (which Docker is) started its journey back in the mainframe world.
Since Docker is a pretty famous topic for blog posts and tutorials these days, I'll reference some good resources to look at, if you’re not familiar with the concepts.
First you can start off at the What is Docker? page which will give you an elevator pitch of containers and the differentiation to hypervisor-based virtualization. Next up, there are the official docker docs, which is pretty great compared to other official documentations. It starts with an installation guide and then goes on to a quick start on containers.
When you want to dive really deep while still getting the 10,000 feet overview I encourage you to check out Nigel Poulton’s pluralsight course called “Docker deep dive”, which starts with a very good overview on the container technologies space before discussing the different technical sub-topics of Docker.
The Benefits of Putting Your CUBA App Into a Container
Before showing the different steps to create a running CUBA container, I want to emphasize the benefits ofin doing that. Actually, it has nothing to do with CUBA in particular, but nevertheless it's worth mentioning.
First of, to get a running “production-like” environment to test on becomes a O(1) alike effort. Why? This is due to the basic principle infrastructure as code which is described in depth in this ThoughtWorks article. Scripting the creation of a server installation like you do with a Dockerfile is a one-time effort. This effort is even very little, because in the case of Docker there is this thing called Docker Hub which lets users share their work in infrastructure scripting like GitHub does for code.
The next thing is that it’s lightning fast. With a one-liner in the shell you can start up a HAProxy in front of two tomcat instances, all backed up by a Postgres cluster and a Redis key-value store. This all will be up and running in a matter of seconds. If you want to remove this test environment it's the exact same effort and you are back in a clean state of your development box without having to maintain different Java versions or Postgres database installations on your OS installation directly by yourself.
The environment described above is not only fast to create but it is also reproducible. It is so valuable to create a situation where development environment = production environment, because a whole category of problems in software development go away. With tools like Docker and again, infrastructure as code, this goal is possible at least.
There are some additional advantages I didn’t cover in this DZone article. So, let's get our hands dirty and create a Docker image that will contain our app.
Create a Container for our CUBA app
Docker’s definition of a container is done via a file called Dockerfile. In this file, you normally start with a base image. These images are often stored in Docker Hub. There are plenty of images for operating systems as well as images for application servers, databases, and applications. In this case, we’ll create an image on the basis of the official Apache Tomcat image.
If you wonder what all of this actually means, you can give it a try.
I assume you are on Linux here. If not, check out docker-machine.
Given you have Docker installed correctly, you run an instance of Tomcat with the following command:
docker run -it --rm tomcat:8-jre8 bash
The first time the Tomcat image will be downloaded and cached on your computer. After this, you have a bash where you can look at the newly created Docker container. Browse the directory structure, create and change config files, look and the Tomcat installation and so on. After typing
exit the bash process will do so and due to the option
--rm, the container will be destroyed with all the data in it.
Executing the same command line once again, a brand new container will be created (this will instead just take a few seconds because the image is already downloaded.) All the changes you made last time will be gone. If you think this is crazy, well this is how containers normally work. If you want to change something, don’t change it in the container itself, but in the Dockerfile that creates it. Otherwise, you are back in the old days with configuration drift, !(reproducible environments) and !(infrastructure as code).
Docker Container Vs Image
If you wonder why I sometimes talk about image and sometimes about container, here’s a short description:
Containers are the running instances of images. The image is the binary like the war file (or exe) and the container is the running process of this binary.
After this brief introduction in image inheritance and container behavior, let's have a look at the dockerfile that decribes our app.
The Dockerfile of Our ordermanagement App
The example project that I want to Dockerize is the ordermanagement app that I created in previous blog posts. Since it is a CUBA 6 app, it will use JRE8 together with Tomcat 8. Let's have a look at the Dockerfile, that describes our image:
### Dockerfile # Base Image: official tomcat 8 image, with jre 8 underneath FROM tomcat:8-jre8 # Add tomcat users file for manager app ADD container-files/tomcat-users.xml /usr/local/tomcat/conf/ # Add generated app war files to the webapps directory ADD war/app.war /usr/local/tomcat/webapps/ ADD war/app-core.war /usr/local/tomcat/webapps/ # Add context config file for the application ADD container-files/context.xml /usr/local/tomcat/conf/ # copy logback.xml config in the container ADD container-files/logback.xml /opt/cuba_home/ # set CATALINA_OPTS env variable to point to logback config file and app home directory ENV CATALINA_OPTS="-Dlogback.configurationFile=/opt/cuba_home/logback.xml -Dapp.home=/opt/cuba_home" # Add postgres jdbc driver lib ADD https://jdbc.postgresql.org/download/postgresql-9.3-1101.jdbc41.jar /usr/local/tomcat/lib/postgresql.jar
First off, you see that the image will be based on the Tomcat 8 image. With ADD, you can copy files from outside the container into it at build time. In this case, we want to override some configuration files for the Tomcat. Next, we add the war files into the webapps directory of the Tomcat.
With ENV, we will create operating system environment variables that Tomcat will pick up. In this case, we will just point the Tomcat to our home directory and the logback config file.
Since we want to connect to a Postgres DB, we’ll add the Postgres drivers into the lib directory of the tomcat installation.
Build the Described Image and Start the First Container
In the example app, there is the subfolder docker-image that contains the Dockerfile as well as the files that are being added. If you want to try it out by yourself follow these steps:
git clone https://github.com/mariodavid/cuba-ordermanagement && cd cuba-ordermanagement ./gradlew buildWar cp build/distribution/war/*.war docker-image/war/ docker build -t cuba-ordermanagement docker-image/
First, clone the repo and build the war files via Gradle. Next, we copy the war files into the docker-image folder. After this, we create the actual Docker container with the Dockerfile above. Additionally, we call the image “cuba-ordermanagement” with the option
-t, so we can reference it later with Docker.
That’s basically it. Now we can create a running instance of this image with the following command:
docker run -it --rm -p 8080:8080 cuba-ordermanagement
-p is the option for port mapping. Since we want to connect into the container, we have to tell the Docker host (in this case our local computer), that it should map a certain port on our host to the container. It's essentially the exact same idea as NAT. The syntax is
After executing this command, the Tomcat will start and crash wonderfully with an
UnknownHostException. Why is this the case? Well, I decided to bake the database connection settings into the image binary. When looking at the context.xml from the repo which is used in the Dockerfile, it requires the host
postgres to be available. It might look a little strange at first sight to not externalize this parameter. But, with this, I can show you another tool in the Docker ecosystem. It’s called Docker compose.
Orchestrate Your Containers With Docker Compose
Docker compose is a tool to create containers and orchestrate them. When you use different features of Docker like Volume mounting or container linking the corresponding command line options become a little tedious. Additionally, these settings are not persisted in any way. Docker compose instead uses a configuration file called docker-compose.yml that describes the different containers and their relationships.
I created a docker-compose.yml file that describes the start of our app as well as a Postgres database container:
web: image: cuba-ordermanagement ports: - "8080:8080" links: - postgres:postgres postgres: image: postgres
web container describes the actual Tomcat instance. It defines the port mapping and an additional link to another container. This
postgres container is another official Docker container (see here). It runs a standard Postgres database with default settings.
The linking on the web container does a few things. But, most importantly, it creates an entry in the
/etc/hosts file with the IP address of the container and the hostname
To run this,
cd into the directory of the
docker-compose.yml file and run:
After this, you should be able to fire up your app at
I hope you've gotten a little insight as to how Docker can be valuable for you. If you have any questions or notes on this tutorial, please leave a comment below.
If you look at what I just presented to you, what we did was basically a lot of plumbing. If this blog post had been written at the end of 2013, everything would be fine. But since it's already 2016 and the world of infrastructure automation is moving so fast, a lot of tools around Docker or related to this technology have arisen. Orchestration mechanisms like docker swarm, mesosphere, or Kubernetes that are like Docker compose with more production environments in mind.
Since the PaaS technology Cloud Foundry got a lot of attraction in the enterprise world in the last few years and was mentioned as a new feature in the CUBA 6 release as being supported, I will tackle this topic in another blog post.