Java Docker Tutorial: Put Tomcat in Docker

Java Docker Container Tutorial

Share This Post

We have to admit it, Java is still a popular programming language. Even if it may lack some of the fanciest features of modern languages, enterprises love it. But the history of Java should not stop us from modernizing our apps. In this Java Docker Tutorial, we will do exactly that, seeing how to create a container for tomcat.

Well, it is a Java Docker Tutorial after all. We assume you already know some Java and some Docker, even the basics. If you do, this is the place where you can glue things together. If you already know Java but you are lacking Docker knowledge, read this Docker tutorial first.

Building our Tomcat Package

In this post, we see how to create a container for a tomcat web application. That is, a web app served over HTTP or HTTPs, made in Java. Tomcat is the webserver application that will run your Java package.

Before we start working on our container, we need to create the .war compressed archive for our app. This is a compressed folder that contains everything about our application, ready to run. Tomcat can read this file and publish the application.

Of course, if you already have a .war file, you are ready to rock. Just skip to the next section,

Maven project structure

The best practice when it comes to building Java packages is to use Maven. This tool takes an XML configuration file that says how to build an application, and then builds it by looking at our project structure.

Of course, in order for all of this to work, you need to scaffold your files properly. This is how you should do it.

This maven project structure allows to put your java application inside a docker container easily
The project structure you should follow for this tutorial.

You should then put all your classes inside the java folder, following the Java package structure. For example, if your classes are in the package org.example, you should create the subfolder org, inside of it, the subfolder example. Then, in this very last folder, you should add your classes.

Inside the WEB-INF folder, you at least want to have the web.xml file. This acts as an entry point for your .war package. It tells tomcat how to interact with your classes. It is in this file that you want to map associations between URLs and Servlets, for example.

And, finally, in the project root, you should have the pom.xml file. This file tells Maven how to build your project.

The web.xml file

Below, an example of web.xml that you may want to use for reference. Of course, you need to edit it if you want it to work. And, you also need to create the relevant classes.

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Example project</display-name>

  <!-- With <servlet>, we define a class that can process requests -->
  <servlet>
    <servlet-name>sampleServlet</servlet-name>
    <display-name>Servlet to demonstrate the project is working</display-name>
    <servlet-class>org.example.demo</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <!-- With <servlet-mapping>, we associate a <servlet> with a URL -->
  <servlet-mapping>
    <servlet-name>sampleServlet</servlet-name>
    <url-pattern>/test-url</url-pattern>
  </servlet-mapping>

</web-app>

The pom.xml

The great thing about pom.xml is that it can help you automate your build process. Think of it as an ancestor of CI/CD pipelines. For example, you can map dependencies inside of it.

Below, an example. You can mainly see three things:

  • Description of the project (e.g. project name)
  • Dependencies
  • Build process and location of the source code

Of course, this example will also need to be adapted to your needs. This is just a Java Docker Tutorial, so going deeper on Maven is out of scope.

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example.demo</groupId>
  <artifactId>demo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>Demo Webapp</name>
  <url>https://accelerates.it</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.4.2</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>5.4.2</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-archiver</artifactId>
      <version>3.4.0</version>
    </dependency>

    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.1</version>
    </dependency>

  </dependencies>

  <build>
    <finalName>${project.artifactId}</finalName>
    <sourceDirectory>src/main/java</sourceDirectory>
    <testSourceDirectory>src/test/java</testSourceDirectory>
    <testResources>
        <testResource>
            <directory>src/test/resources</directory>
        </testResource>
    </testResources>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <!-- Axis 2 build -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.1</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

Create your WAR Package

Once you are all set, in your project root you can run mvn war:war to call Maven from the command line. This will tell tomcat that you want to get the .war package.

mvn war:war

This will create your package in the target folder by default.

Java Docker Tutorial: The Dockerfile

The Dockerfile is the build recipe for any container. It tells docker what to put inside of a container image. It is just a text file where you list a series of operations. So, in your project root, create a dockerfile file (without any extension). Then, edit it with your favorite text editor (I love Microsoft VS Code, but Sublime is also a great choice).

The Basics of our Java Docker Container

The very first line of our Dockerfile should tell on which existing container image we want to base our container. We want to keep it simple, so we want to create a Linux Debian container, stripped out of any unnecessary stuff. So, the first line goes like this.

FROM debian:stretch

Then, we need to add all the dependencies that we need. For sure, we will need the Java Runtime (JRE), WGET and CURL to install tomcat, and unzip to work with our WAR package. I also addlibtcnative-1, which improves tomcat performance in production.

Just after that, we need to define the environment variable for JAVA_HOME, mandatory to run Java. Since we are installing Java 8 OpenJDK with the default-jre, we use the path you find below.

# Install packages
RUN \
    apt-get update && \
    apt-get upgrade -y && \
    apt-get install -y default-jre && \
    apt-get install -y libtcnative-1 && \
    apt-get install -y wget && \
    apt-get install -y curl && \
    apt-get install -y unzip && \
    apt-get install -y gettext-base
ENV JAVA_HOME="/usr/lib/jvm/java-8-openjdk-amd64/jre"

All good, now we need to install Tomcat.

How to Install Tomcat in a Docker Container

This is tricky but will save you a lot of time. Say we want Tomcat 9 in our application, but always to the latest patch and minor version. We need to download it from the Apache website, but the URL changes as the version progresses. Even worse, the URL of the non-latest version gets deleted whenever a new version comes out.

So, in this Java Docker Tutorial, we will see how to do things dynamically. We want docker to discover dynamically the latest version of tomcat, and use that one, every time we build the container image. We can do just that with the lines below.

RUN \
    TOMCAT_VER=`curl --silent http://mirror.vorboss.net/apache/tomcat/tomcat-9/ | grep v9 -m 1 | awk '{split($5,c,">v") ; split(c[2],d,"/") ; print d[1]}'` && \
    wget -N http://mirror.vorboss.net/apache/tomcat/tomcat-9/v${TOMCAT_VER}/bin/apache-tomcat-${TOMCAT_VER}.tar.gz &&\
    tar xzf apache-tomcat-${TOMCAT_VER}.tar.gz && \
    rm -f apache-tomcat-${TOMCAT_VER}.tar.gz && \
    mv apache-tomcat-${TOMCAT_VER}/ /opt/tomcat

First, we fetch what is the latest version by extrapolating a string from the website. We store that in the TOMCAT_VER environment variable. Then, we use that to fetch the compressed file, extract it, and put it into the /opt/tomcat folder.

Just after that, we can set the CATALINA_HOME and add it also to the path, so that we can start tomcat easily from any place of the terminal.

ENV CATALINA_HOME="/opt/tomcat" \
    PATH="$PATH:/opt/tomcat/bin"

Copying the WAR file in the Docker Container

We need to add some flavor to our tomcat, we need to add our application. We do that by copying the .war file into the folder containing web apps. Before that, however, we ensure no junk default app is present.

RUN rm -fr /opt/tomcat/webapps/*

Now we can actually do the copying.

COPY target/my-package-name.war /opt/tomcat/webapps/my-package-name.war

With this code, your application will be served at http://whatever/my-package-name. Instead, if you want it to be at the root of your website (e.g. http://whatever/), you should rename it to ROOT, like below.

COPY target/my-package-name.war /opt/tomcat/webapps/ROOT.war

You may also want to not copy the .war package as it is inside the web apps folder. Instead, you may want to extract it and copy the uncompressed folder. This is useful if you want to do some final adjustments, like injecting some other files inside it. If you want to do it, you should do it in the following way.

RUN mkdir /etc/temp
COPY in/target/my-package-name.war /etc/temp/my-package-name.war
RUN \
	unzip /etc/temp/my-package-name.war -d /opt/tomcat/webapps/my-package-name && \
	rm -fr /etc/temp

Okay, we are almost good, but now we need to spice our tomcat even a little bit more.

Configure Tomcat inside Docker

The best option we have is to create an XML file in the root of our project and name it tomcat-conf.xml. In that file, we put all the configuration for tomcat, and then we use this file to override the default configuration that is already inside tomcat.

Again, this is a Java Docker Tutorial, so our goal here is not to learn how to configure Tomcat. Rather, we want to learn how to change its configuration inside Docker. Easy enough, we use the following command to copy the file. Note that the destination file and name must be exact.

COPY tomcat-conf.xml /opt/tomcat/conf/server.xml

We are almost all set, now we need to wrap up our container.

Finishing our Java Docker Container

At the end of our Dockerfile, we need to define what command to launch when starting the container, and which ports to expose. As a command, we obviously want to start tomcat. As ports, we normally expose port 80 and 443 by default, but you can tune it to adjust your needs.

We do that with the following lines.

CMD ["catalina.sh", "run"]
EXPOSE 80 443

Note that these are the default settings of the container image. You can override those when starting up a container if you want to.

Java Docker Tutorial Conclusion

In the end, Pushing a Java Tomcat application inside Docker is just a matter of the Docker file. In this Java Docker Tutorial, we saw how to do that by going step-by-step on our Dockerfile.

For your convenience, check the entire Dockerfile with comments on GitHub.com.

Picture of Alessandro Maggio

Alessandro Maggio

Project manager, critical-thinker, passionate about networking & coding. I believe that time is the most precious resource we have, and that technology can help us not to waste it. I founded ICTShore.com with the same principle: I share what I learn so that you get value from it faster than I did.
Picture of Alessandro Maggio

Alessandro Maggio

Project manager, critical-thinker, passionate about networking & coding. I believe that time is the most precious resource we have, and that technology can help us not to waste it. I founded ICTShore.com with the same principle: I share what I learn so that you get value from it faster than I did.

Alessandro Maggio

2020-07-30T16:30:48+00:00

Unspecified

DevOps

Unspecified