はじめに

Spring Bootなら、TomcatがJARに内蔵されていて、わざわざ、Tomcatを建てて、そこにWARを置くなんてことはしなくていい。

ただ、現実問題、Javaの仕事では超レガシーなシステムを扱うことが多く、TomcatにWARを置くスタイルの構成になっていることはまあまあある。

ローカルで動かすのも、Eclipseを起動して、Tomcatをインストールして、Tomcatの設定をして、実行して……ってやるのは面倒だ。レガシー環境だと大体がこうであり、現在進行形で苦しんでいる人もいるだろう……

今回、とりあえず、ローカルで簡単に実行環境を使えるように、DockerでTomcatサーバを建てて、そこにWARを自動で置くようにする。

記事の内容を反映したプロジェクトを配布すれば、各開発PCにTomcatをインストールして、バージョンやデプロイ先などを設定する手間は避けられる。(もっと根本から修正した方がいいと思うが……)

サンプルServletアプリ

サンプルとして以下のような簡単なServletをsrc/jp/yukio0/sample/HelloServlet.javaに作成した。

package jp.yukio0.sample;

import java.io.IOException;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet("/")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(
            HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/plain; charset=UTF-8");
        response.getWriter().println("こんにちは、Docker上のTomcatです!");
    }
}

Dockerfile

WAR作成用のステージ

まず、最初のステージでは、Tomcat 10.1とJDK 21を含むイメージを使用し、WARを作成する。

プロジェクトルート直下に以下内容のDockerfileを作成する。

# WAR作成用ステージ
FROM tomcat:10.1-jdk21-temurin-noble AS builder

WORKDIR /build

# Javaソースをコピー
COPY src ./src

# JavaソースをコンパイルしてWARを作成
RUN mkdir -p work/WEB-INF/classes \
    && javac \
        -encoding UTF-8 \
        -cp /usr/local/tomcat/lib/servlet-api.jar \
        -d work/WEB-INF/classes \
        src/jp/yukio0/sample/HelloServlet.java \
    && jar \
        --create \
        --file sample.war \
        -C work \
        .

使用しているベースイメージは、Tomcat 10.1とJava 21を含むTomcat公式イメージ。レガシーシステムでは、実際には、もっと低いバージョンになると思う。

AS builderでこのビルドステージにbuilderという名前を付ける。

FROM tomcat:10.1-jdk21-temurin-noble AS builder

その後、Dockerコンテナ内で、WARをビルドしている。

RUN mkdir -p work/WEB-INF/classes \
    && javac \
        -encoding UTF-8 \
        -cp /usr/local/tomcat/lib/servlet-api.jar \
        -d work/WEB-INF/classes \
        src/jp/yukio0/sample/HelloServlet.java \
    && jar \
        --create \
        --file sample.war \
        -C work \
        .

ここでは、javacjarコマンドを使ってビルドしているが、いくらレガシーな環境とはいえ、AntMavenは使っていると思う。適宜、ビルドコマンドは変えること。

Tomcat実行用ステージ

次に、Tomcat起動+WAR配置の内容を書く。以下をDockerfileに追記する。

# Tomcat実行用ステージ
FROM tomcat:10.1-jre21-temurin-noble

# デフォルトのWebアプリケーションを削除
RUN rm -rf /usr/local/tomcat/webapps/*

# ビルド用ステージで作成したWARを配置
COPY --from=builder \
    /build/sample.war \
    /usr/local/tomcat/webapps/ROOT.war

EXPOSE 8080

次の処理では、Tomcatのwebappsディレクトリを空にしている。

RUN rm -rf /usr/local/tomcat/webapps/*

続いて、プロジェクト直下のsample.warをTomcatへコピーする。

COPY --from=builder \
    /build/sample.war \
    /usr/local/tomcat/webapps/ROOT.war

Dockerfile全体

Dockerfileは以下内容になる。

# WAR作成用ステージ
FROM tomcat:10.1-jdk21-temurin-noble AS builder

WORKDIR /build

# Javaソースをコピー
COPY src ./src

# JavaソースをコンパイルしてWARを作成
RUN mkdir -p work/WEB-INF/classes \
    && javac \
        -encoding UTF-8 \
        -cp /usr/local/tomcat/lib/servlet-api.jar \
        -d work/WEB-INF/classes \
        src/jp/yukio0/sample/HelloServlet.java \
    && jar \
        --create \
        --file sample.war \
        -C work \
        .

# Tomcat実行用ステージ
FROM tomcat:10.1-jre21-temurin-noble

# デフォルトのWebアプリケーションを削除
RUN rm -rf /usr/local/tomcat/webapps/*

# ビルド用ステージで作成したWARを配置
COPY --from=builder \
    /build/sample.war \
    /usr/local/tomcat/webapps/ROOT.war

EXPOSE 8080

.dockerignore

Dockerイメージの作成に不要なファイルを除外するため、プロジェクトルート直下に以下内容の.dockerignoreを作成する。

# Git
.git

# IntelliJ IDEA
.idea
*.iml

# Eclipse
.project
.classpath
.settings
bin
.externalToolBuilders
.apt_generated
.apt_generated_tests

# ローカルで作成した中間生成物
work
sample.war
jakarta.servlet-api_6.0.0.jar
*.class

compose.yaml

docker composeを使うために、プロジェクトルート直下に以下内容のcompose.yamlを作成する。

services:
  tomcat:
    build:
      context: .
    container_name: docker-tomcat
    ports:
      - "8080:8080"

ホスト側の8080番ポートを、Tomcatが使用するコンテナ側の8080番ポートへ接続している。

ports:
  - "8080:8080"

左側がホスト側、右側がコンテナ側のポート番号になる。

コンテナを起動する

プロジェクトのディレクトリに移動した後、次のコマンドを実行する。

docker compose up --build

これで、http://localhost:8080にアクセスする。

docker-tomcat

おわりに

これで、ローカル環境でTomcatをDockerから起動できるようになった。レガシーシステムをモダン化していく第一歩として、まずは実行環境のDocker化から試してみるのはいいかもしれない。

Docker Compose Watchを使えば、Javaソースの変更を検知して、自動的に再ビルドできるようだ。これについては、また今度書こうと思う。