Docker Tomcat container SSH tips and tricks

Posted by Leo Daidone on 5/23/2017

Intro

Hello! In this post we’ll discuss a real customer use case and talk about some of the issues we solved in the process.

The customer’s architecture requirements included interfacing with a third party service into our custom PHP framework code. As the customer’s requirements also stipulated the service code must be written in Java (or .Net) we had to introduce Tomcat into the equation.

Even though PHP and Tomcat shared the server on the initial system, we decided to wrap all services within their own Docker containers.

Creating a Docker container for Tomcat

Let’s start with a docker-compose file that will define our tomcat container. We used tomcat:8.5.13-jre8-alpine as the base image.

At first glance this file contains 3 big sections, volumes, ports and expose.

The Volumes section will map files and folders needed to run a tomcat instance, included our app. You will notice in the example below that I included, in addition with .war file, JKS certificates and some configuration files I will explain later in this post.

The ports section will just bind container’s listen port with internal tomcat port (8080).

In the expose section we open some ports, in particularly 8009, that is required for server admin.

version: '2.1'

services:
  my_tomcat:
    container_name: my_tomcat
    image: tomcat:8.5.13-jre8-alpine
    volumes:
      - ./conf/catalina.properties:/usr/local/tomcat/conf/catalina.properties
      - ./client-keystore.jks:/etc/tomcat/client-keystore.jks
      - ./truststore.jks:/etc/tomcat/truststore.jks
      - ./myApp.war:/usr/local/tomcat/webapps/myApp.war
    env_file: ./environment.env
    ports:
      - "8080:8080"
    expose:
      - "8009"
      - "8080"

Setting our environment vars

As you may have noticed, we isolated the environment variables out of the docker-composer, in a new environment.env file, specified in env_file.

In addition to defining container variables, it will define JVM  requirements, such as JAVA_OPTS.

SHELL=/bin/sh


TOMCAT7_GROUP=tomcat7

TOMCAT_CFG_LOADED="1"

JAVA_OPTS="-Djavax.net.debug=ssl,handshake,keymanager,verbose  -Dendpoint=https://3rd-party.domain.com/services/api.wsdl -Djavax.net.ssl.keyStore=/etc/tomcat/client-keystore.jks -Djavax.net.ssl.keyStorePassword=secret -Djavax.net.ssl.trustStore=/etc/tomcat/truststore.jks -Djavax.net.ssl.trustStorePassword=changeit -Djava.awt.headless=true -Xms512m -Xmx2048m -XX:+UseConcMarkSweepGC -Dhttps.protocols=TLSv1,TLSv1.1,TLSv1.2 -Djdk.tls.client.protocols=TLSv1.1,TLSv1.2 -Dsun.security.ssl.allowUnsafeRenegotiation=true"

SECURITY_MANAGER="false"

For this example, we’re telling the JVM to show verbose debug info for ssl and keymanager use (-Djavax.net.debug), which is the endpoint of the service (-Dendpoint), keystore and truststore files and passphrases repectively (-Djavax.net.ssl.keyStore, -Djavax.net.ssl.keyStorePassword, -Djavax.net.ssl.trustStore and -Djavax.net.ssl.trustStorePassword), among others.

Unfortunately, this is not enough with Alpine based container. In shortest explanation, when you start the container it runs catalina.sh that loads default configuration from the image.

But don’t panic, here’s the trick: the custom configuration can be placed in the catalina.properties file, letting those vars available for the system to be accessed like below:

System.getProperty("endpoint");

These custom properties file will be mounted as volume, replacing the default one and voila.

./conf/catalina.properties:/usr/local/tomcat/conf/catalina.properties

Adding and managing a Keystore

Once you have created your certificates stores (there is a powerful tool provided by Java, the keytool, which will be a topic for our future blog post), just copy the catalina.properties from standard tomcat and append variables at the end:

endpoint=https://3rd-party.domain.com/services/api.wsdl
javax.net.ssl.keyStore=/etc/tomcat/client-keystore.jks
javax.net.ssl.keyStorePassword=secret
javax.net.ssl.trustStore=/etc/tomcat/truststore.jks
javax.net.ssl.trustStorePassword=changeit

Final Thoughts

While this was a unique project, the common solution often works well. When I ran into common issues I also found a lot of recommendations for adding or updating a JAVA_OPTS environment variable. We did end up improvising to a point, but this was mostly reserved for edge cases.

I am glad you’re still here! Thank you and hope you have enjoyed reading. See you in next posts.