Docker Compose
May 15, 2024

Preparing a Docker Compose Environment

Java Application Development

Docker, the #1 choice for containerization in Java microservices applications, is one of the most-used tools in Java development today. But how developers deploy and orchestrate their Docker-based microservices can vary. Orchestrating via Kubernetes or a Docker Compose environment can each carry particular benefits.

In this blog, we'll look at Docker Compose, environment variables, and provide a demonstration on how to set up your first Docker Compose environment. 

Back to top

What Is Docker Compose?

Docker Compose is a powerful tool that allows developers to define and run multi-container Docker systems. 

Related Reading >> Docker Microservices in Java

As the name implies, Docker Compose is dependent on Docker—a platform that delivers software packages in separate containers. A Docker Compose environment uses YAML files for configuring app services and allows developers to run concurrent commands on multiple containers.

Back to top

Do You Need to Use Docker for Java?

Developers don't need to use Docker for Java, or any language for that matter. However, Docker adds a layer of convenience in packaging multiple applications and services into a single container. It also provides development teams with the ability to distribute deployment processes to their entire team effortlessly.

Back to top

How to Prepare a Docker Compose Environment

It's simple to prepare a Docker Compose environment for Java development. For this example, we are using an application called Petclinic, which uses an embedded H2 database. In some scenarios, Petclinic talks to the Supplements application over HTTP. Supplements uses MongoDB to fetch JSON data to send back to the Petclinic app, if requested.

It's not necessary to build a custom Docker image. Instead, use the stock images for Tomcat and MongoDB. To configure all the pieces together and to escape command line horror, we will use Docker Compose.

Back to top

1. Installing Docker Compose

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 [container IP]:8000

Back to top

2. Deploying the Web Application Into a Docker Container

Using docker volumes is convenient when the artifact has to be updated frequently, which means we do not need to rebuild the image. It would be 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. 

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.

Back to top

3. Run MongoDB in a Docker Container

Running MongoDB in a Docker container is even simpler than running Tomcat:

db:
  image: mongo
  command: -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.

Back to top

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: -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, and supplements can link to db. 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; it will also 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.

Back to top

5. Specifying Docker Compose 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 Docker Compose environment variable via System.getProperty.

Back to top

6. Configuring JRebel

Volumes and environment configuration are the two elements that we need to configure a Java agent. This time we want to use JRebel to enable immediate reflection of changes in Docker images without having to start the image each time. 

First, you will need to download JRebel to your machine. You can do this with the following curl statement:

Create a Dockerfile in your project directory. Let's name it: 

Dockerfile.jrebel

Add the following content to the Dockerfile.jrebel: 

FROM alpine:latest 
 
# Install curl 
RUN apk add --no-cache curl 
 
# Download JRebel 
RUN curl -O http://dl.zeroturnaround.com/jrebel-stable-nosetup.zip 

In your docker-compose.yml, define a service that builds from this Dockerfile: 

version: '3' 

services: 
 jrebel_downloader: 
   build: 
     context: . 
     dockerfile: Dockerfile.jrebel 

Now, when you run docker-compose up, Docker Compose will build a Docker image based on the Dockerfile.jrebel, which includes the curl command to download the JRebel ZIP file. 

After running docker-compose up, you will have a container with the JRebel ZIP file downloaded into it. You can then modify your Dockerfile or Docker Compose configuration to include further steps like unzipping the JRebel ZIP file or performing other actions with it, depending on your requirements. 

 curl -O http://dl.zeroturnaround.com/jrebel-stable-nosetup.zip 

unzip jrebel-stable-nosetup.zip 

We just need one more entry to specify the location of jrebel.jar file: 

volumes: 
       - ./jrebel.jar:/jrebel.jar 
    - ./lib/libjrebel64.so:/lib/libjrebel64.so

Add the -javaagent VM argument to JAVA_OPTS environment variable for both Tomcats: 

- "JAVA_OPTS=-agentpath:/lib/libjrebel64.so -Drebel.remoting_plugin=true -Drebel.log.file=/jrebel.log -Dmongo.host=db"

So the full configuration for the Petclinic container will look like this: 

version: "3.9" 
services: 
petclinic: 
  image: tomcat 
  ports: 
    - "8000:8080" 
  links: 
    - supplements 
    - db 
  volumes: 
 
   - ./jrebel.jar:/jrebel.jar 
    - ./lib/libjrebel64.so:/lib/libjrebel64.so 
    - ./petclinic.war:/usr/local/tomcat/webapps/petclinic.war 
  environment: 
    - - JAVA_OPTS=-agentpath:/lib/libjrebel64.so -Drebel.remoting_plugin=true -Drebel.log.file=/jrebel.log -Dsupplements.host=supplements" 

Make sure to start your docker instance to reflect the modifications in your application:

$ docker-compose down 
$ docker-compose up
Back to top

Final Thoughts

Docker Compose is a wonderful tool that streamlines the process of setting up complex environments for production or for demo purposes. In this post we looked at how Docker Compose enables you to easily prepare a demo environment that can be shared with the team, all without building your own images, which saves you valuable development time. 

Looking for more time savings? Try JRebel. By eliminating redeploys, your team could save upwards of a month of development time annually. 

Want to see how much time your Java development team could save by eliminating redeploys? Try JRebel for free for 14 days. 

Try JRebel for Free

Note: This post was originally published on April 11, 2016 and has been updated for accuracy and comprehensiveness.

Back to top