I’m the product owner of XRebel — the profiler for Java web applications that developers can use while they’re actually developing their applications. Recently I had to prepare a demo environment that I could easily share with my team mates. If the demo would have consisted of just a web app, without external dependencies, it would have been easy to share with the team: I’d just throw a WAR up on Dropbox and everybody would be free to do whatever they wanted with it. However in my scenario, I had an external dependency on MongoDB which made my demo a little less portable.
Naturally, I thought that Docker might be a good solution for this. The prerequisite though, is that my team members would have to install the Docker Toolbox in order to use the environment. But this is a wise thing to do anyway, isn’t it? Docker is an established container management system, with tons of resources available to learn the basics. We even recently published a Docker commands and best practices cheat sheet from which you can learn everything you need to navigate your way around Docker containers.
Let me sketch this out for you. The application is Petclinic – a web application, with a UI, that uses an embedded H2 database. In some scenarios, Petclinic talks to the Supplements application over HTTP. Supplements uses MongoDB to fetch data as JSON back to the Petclinic app, if requested.
For this task, building a custom Docker image is overkill. There are the stock images for Tomcat and MongoDB, which is a perfect fit for my scenario! To configure all the pieces to work together we will use Docker Compose to escape the command line horror.
With Docker Compose we need a
docker-compose.yml file, which allows us to specify configuration in a declarative manner.
Start with the minimal configuration for Tomcat:
petclinic: image: tomcat ports: - "8000:8080"
This means we are declaring a
petclinic container instance that uses a
tomcat image. By executing the
docker-compose up command we are now able to start a container that hosts a Tomcat instance. By default, Tomcat starts on port 8080. By using the “ports:” attribute in the configuration we have exposed container’s port 8000 that points to Tomcat’s port 8080. Voila! Tomcat is running and is accessible at
[virtual machine IP]:8000
How to find out the IP of a virtual machine?
To determine the IP address of a virtual machine that runs docker container, execute the following command:
docker-machine ip default
Deploying a web application into Docker container
Now, it would be quite cool to deploy a pre-built web application to a Tomcat instance running in that Docker container. Volumes are helpful in this situation, specified under the “volumes:” attribute in our configuration file.
Using docker volumes is convenient when the artifact has to be updated frequently – this means we do not need to rebuild the image.
petclinic: image: tomcat ports: - "8000:8080" volumes: - ./petclinic.war:/usr/local/tomcat/webapps/petclinic.war
The mapping is of course specific to this application. For instance, Tomcat’s
webapp directory is located at
/usr/local/tomcat/webapp. This is the path we need to use to be able to deploy our web application.
The format for the mapping is
[local path]:[remote path]. The assumption here is that the
petclinic.war file is located in the same directory from which the docker-compose command is executed.
MongoDB with Docker
Running MongoDB in a Docker container is even more trivial than running Tomcat:
db: image: mongo command: -smallfiles -nojournal
There is a stock image available which includes MongoDB. The “db” instance of the container runs the MongoDB process with the default port exposed for our convenience.
Linking Docker containers
The assumption is that every individual application will be deployed in its own instance of Tomcat, running in a dedicated container instance. The caveat here is that the containers have to be linked for the applications to be visible to each other. The “links:” attribute in
docker-compose.yml is exactly how we can achieve this.
petclinic: image: tomcat ports: - "8000:8080" links: - supplements volumes: - ./petclinic.war:/usr/local/tomcat/webapps/petclinic.war environment: - "JAVA_OPTS=-Dsupplements.host=supplements" supplements: image: tomcat ports: - "8888:8080" links: - db volumes: - ./supplements.war:/usr/local/tomcat/webapps/supplements.war environment: - "JAVA_OPTS=-Dmongo.host=db" db: image: mongo command: -smallfiles -nojournal
The “links:” attribute declares that the the given container(s) can link to the other specified containers. In our case,
petclinic can link to
supplements can link to
The cool part is that the symbolic name (e.g. “petclinic”) assigned to the container instances can be used for more than just linking containers, but also it will be resolved as an argument. This is how we can use it to provide the additional configuration parameter to our web applications via environment variables.
To pass additional environment variables, the “environment:” attribute is used in
docker-compose.yml. For Tomcat we can specify JAVA_OPTS environment variable to pass any extra configuration parameters. In the example we’re passing the symbolic name of the linked container for the application to be able to access the linked resource via a network call:
environment: - "JAVA_OPTS=-Dmongo.host=db"
It adds a requirement to the application to use the environment variable via System.getProperty.
Volumes and environment configuration are the two elements that we need to configure a Java agent. This time I wanted to use XRebel to profile end-to-end transactions for the applications running in Docker.
So I’d just need one more entry to specify the location of
volumes: - ./xrebel.jar:/xrebel.jar
And also add the -javaagent VM argument to JAVA_OPTS environment variable for both Tomcats:
- "JAVA_OPTS=-javaagent:/xrebel.jar -Dmongo.host=db"
So the full configuration for the Petclinic container will look like this:
petclinic: image: tomcat ports: - "8000:8080" links: - supplements - db volumes: - ./xrebel.jar:/xrebel.jar - ~/.xrebel/xrebel.lic:/root/.xrebel/xrebel.lic - ./petclinic.war:/usr/local/tomcat/webapps/petclinic.war environment: - "JAVA_OPTS=-javaagent:/xrebel.jar -Dsupplements.host=supplements"
You can find this demo in our GitHub repository. If you want to try it out, clone the repository and execute
docker-compose up. You’ll have all the applications up and running in no time and will be able to tinker with the docker scripts if you want.
Docker Compose is a wonderful tool that makes setting up complex environments for production or for demo purposes almost trivial and repeatable way. In this post we looked at how docker compose enables you to:
- easily prepare a demo environment that can be shared with the team
- escape the need to build your own images, write Dockerfile, or tinker with docker command line arguments