はじめに
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 \
.
ここでは、javacとjarコマンドを使ってビルドしているが、いくらレガシーな環境とはいえ、AntやMavenは使っていると思う。適宜、ビルドコマンドは変えること。
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にアクセスする。
おわりに
これで、ローカル環境でTomcatをDockerから起動できるようになった。レガシーシステムをモダン化していく第一歩として、まずは実行環境のDocker化から試してみるのはいいかもしれない。
Docker Compose Watchを使えば、Javaソースの変更を検知して、自動的に再ビルドできるようだ。これについては、また今度書こうと思う。
