Dockerizing a Tomcat + PostgreSQL Java web application

Generally, a docker container is meant to hold exactly one application. For a typical Java web application (in this example we assume a Tomcat 8 servlet container and a Postgres 9.4 database), this will lead to two and a half containers:

  • The web container for our web application (in the example we assume .WAR packaging) deployed to Tomcat
  • The db container for our Postgres database
  • The db-data container. This one is necessary because Docker containers are volatile by default. Thus, data stored in a container is lost when they are restarted, so we need a volume to permanently store the database data.

Naturally, the applications within their respective containers need to communicate with each other. The current tool of choice to enable inter-container communication is Docker Compose.

Directory and file structure

The directory structure for our Docker files looks like this:

project  
│   docker-compose.yml
│
└───db
    │   Dockerfile
    │
├───web
    │   Dockerfile
    │   application.war

The database container

We will begin with the Dockerfile of our database in ./db/Dockerfile:

FROM postgres:9.4  
MAINTAINER lpradel

ENV POSTGRES_USER admin  
ENV POSTGRES_PASSWORD password  
ENV POSTGRES_DB app_staging  

In this example we are using the standard Postgres 9.4 image and configure credentials for a single user and DB.

The application container

The Dockerfile for our web container in ./web/Dockerfile looks like this:

FROM tomcat:8-jre8  
MAINTAINER lpradel

RUN echo "export JAVA_OPTS=\"-Dapp.env=staging\"" > /usr/local/tomcat/bin/setenv.sh  
COPY ./application.war /usr/local/tomcat/webapps/staging.war

CMD ["catalina.sh", "run"]  

The image is based on the standard Tomcat 8 image. I have included an example of how to perform basic Tomcat configuration. Here, we pass some JAVA_OPTS to the setenv.sh shell script of Tomcat.

Finally, our application .WAR is copied the to the Tomcat webapps directory and deployed in the staging context.

Docker Compose

The last step is orchestrating our containers in ./docker-compose.yml:

app-web:  
  build: ./web
  ports:
    - "8081:8080"
  links:
    - app-db

app-db:  
  build: ./db
  expose:
    - "5432"
  volumes_from:
    - app-db-data

app-db-data:  
  image: cogniteev/echo
  command: echo 'Data Container for PostgreSQL'
  volumes:
    - /var/lib/postgresql/data

We bind the default Tomcat port 8080 in the container to the 8081 port of our host system and expose the Postgres port 5432 to the web container which is linked to it. Lastly, we reference the permanent data volume as a volume for our db container.

Running the application

Execute the following command to start our containers:

$ docker-compose up -d 

To stop the containers run

$ docker-compose stop

A list of all docker containers is available via

$ docker ps -a

To open a shell in one of the containers (for example to access log files), run the following command:

$ docker exec -it <container_id> /bin/bash 

Finally, to redeploy only specific containers, use the following command:

$ docker-compose build <container_id>
$ docker-compose up --no-deps -d <container_id>