Docker compose is an incredibly useful farmework that helps in binding and running multi-container Docker applications. Many companies these days have switched to Docker for running and deploying their applications/services on the cloud. While containers are useful, it becomes a pain to manage and orchestrate a muti-container docker application. Binding mutiple Docker containers to a single network and specifying the dependency of one network on another is a challenge in itself. Docker compose helps us solve this problem by providing a yaml template to specify the details of each container and all containers can be run using a single docker compose command that simply reads this yaml file.

In the example below, we will be listing all the steps and configuration needed to deploy our Java based application, packaged as a WAR file along with Microsoft SQL server database running on different containers such that the web application's container is able to talk to the SQL server database container. We'll be using an RHEL based linux server to deploy these containers on. Pre-requisites like installing Docker and Docker Compose have been omitted. While you may not abe able to follow the tutorial and deploy the same app, the basic configuration would stay the same for any Java application being deployed on Tomcat and that uses Microsoft SQL server as the database.

The first step is to build the individual Dockerfile for you tomcat application. A simple Dockerfile configuration listed below would be sufficient.

FROM tomcat:9.0-alpine

ENV JAVA_OPTS="-Xms2g -Xmx4g"

EXPOSE 8080
CMD ["catalina.sh", "run"]
Dockerfile

The base docker image being used by the Dockerfile is based on Alpine Linux and suitable for running tomcat apps. Note that any additional JVM arguments can be added to JAVA_OPTS. For now we've only defined the starting and max heap size.

To build an image from the above dockerfile, run the below command in the same directory as the dockerfile.

sudo docker build -t webapp .
Build docker image for the webapp

This builds a new docker image called "webapp" and we'll be able to use it to deploy our tomcat application.

Next, we need to write the docker compose file which is the crux of it all.

version: '3.3'
services:
  mssql:
    image: mcr.microsoft.com/mssql/server:latest
    volumes:
      - ~/mssqlserver:/var/opt/mssql
    ports:
      - "1433:1433"
    user: root
    expose:  
      - 1433
    environment: 
      - ACCEPT_EULA=Y
      - MSSQL_PID=Express
      - SA_PASSWORD=Hello@1234
      - cap-add=NET_ADMIN
    networks:
      - technjektion
  webapp:
    depends_on:
      - mssql
    user: root
    image: webapp
    volumes:
      - ~/deploy:/usr/local/tomcat/webapps/
    ports:
      - '80:8080'
    links:
      - mssql
    networks:
      - technjektion
networks: 
  technjektion:
    driver: bridge
Docker Compose file to bind the webapp with the database

The above configuration will pull the latest Microsoft SQL server image and add a volume to it. Here are some of the major tag definitons for the compose file:

  1. version - Used to provide the docker compose version that the yaml file is made for. This has been made optional now and isn't required anymore.
  2. services - The parent tag to define different containers/services under it.
  3. mssql - Defines the container to be used for Microsoft SQL server database.
  4. webapp - Defines the container to be used for the tomcat based web application.
  5. networks - Is needed to bing the two containers together so that they can communicate with each other.
  6. image - Specifies the base image to build the docker containers from.
  7. volumes - Used to connect a directory on the host machine with another directory on the container. This is needed for persisting data when the containers are stopped or removed.
  8. user - The linux user to perform all actions inside the container. Root is used to avoid permission issues.
  9. ports - Is ued to map a port number on the host with a port on the container. The syntax is <host_port>:<container_port>
  10. expose - Another way to open a port to the host from the container.
  11. environment - Used to define environment variables. Varies depending on the container.
  12. networks (within the service) - Specified the list of networks that the container is connected to.
  13. links - The most important parameter which allows the webapp to talk to the database container by using the hostname defined here. "mssql" is going to be the hostname for the database. The JDBC connection string for Java apps should point to this hostname istead of "localhost".

The volume in "volumes:" is used to persist the SQL data on the host server as everytime the container is restarted the data gets lost unless it has been attached to a volume which is saved on the host machine. ~/mssqlserver is the path to the host folder that is mapped to /var/opt/mssql on the container. Note that instead of providing a relative path you can also provide an absolute path to the volume directory by using the full path instead. Similary, another volume is needed for tomcat to deploy the war file. Instead of adding the war to the built container image, we will simply place the war in the shared directory defined in the "volumes" i.e. the folder "deploy".

The next step would be to place the WAR file in the "~deploy" folder so that it is shared with the tomcat container when it is run. Also, we'd need to create any databases needed for the web application by getting in to the SQL container. A bash shell can be opened in the SQL container by running.

sudo docker exec -it sql1 "bash"

Where "sql1" is the name of the running SQL container. You can also restore your MS SQL database by directly copying it to the ~/mssqlserver/data directory on your host.

Finally we need to bring up the services by running

sudo docker-compose up

in the same directory that has the docker-compose.yml file. Make sure to add the "-d" flag to the command if you don't want the process to be attached to the shell. A good idea would be to skip it the first time to watch out for any connection errors and omit it the next time onwards.

Running the above command brings up both containers and with the ability to enable container to container communication.

Run the below command to verify that both containers are running

sudo docker-compose ps