Amazon Corretto 21をベースイメージにしたApache Tomcat 10.1のDockerイメージを作る

# What's？

Apache Tomcatには、公式のDockerイメージがあります。

[dockerhub / tomcat](https://hub.docker.com/_/tomcat)

現時点でのApache Tomcatの最新バージョンは11ですが、ここで使われているOpenJDKディストリビューションはEclipse Temurinのみです。

今回はAmazon CorrettoをベースイメージにしたApache TomcatのDockerイメージを作ってみたいと思います。

# どうしてEclipse Temurinのみなの？

実際にDockerイメージを作る前に、Apache TomcatのDockerイメージがどうしてEclpse Temurinのみなのか少し見てみました。

ちなみに、Apache Tomcat 9.0まではAmazon Correttoを使ったDockerイメージもあったんですよね。

実はApache Tomcat 10.0.27まではAmazon CorrettoのDockerイメージもあったようです。

https://hub.docker.com/layers/library/tomcat/10.0.27-jdk17-corretto-al2/images/sha256-2f967f344e9c008a2beba20856bb0999e16fadc15ea5307e1fef317ebb29f17a

ではどうしてないのかというと、Amazon Correttoや他のディストリビューションのJDK／JREを組み合わせてDockerイメージを作成すると、パターンが膨大になりすぎて制御不能になるから、だそうです。

https://github.com/docker-library/tomcat/pull/299

https://github.com/docker-library/tomcat/issues/330

https://github.com/docker-library/tomcat/pull/248

というわけで、Amazon Correttoを使ったApache TomcatのDockerイメージが再度出てくる可能性はかなり低いのではないかなと思います。

# 参考にするもの

では、Amazon Correttoを使ったApache TomcatのDockerイメージを作りたいわけですが、参考にするのはこちらのGitHubリポジトリでしょう。

https://github.com/docker-library/tomcat

今回は、現在リリースされているJakarta EE 10に対応するApache Tomcat 10.1を対象に考えたいと思います。

JDK、JRE（11、17、21）とUbuntu Linux 22.04 LTS、24.04 LTSの4つの組み合わせの選択肢がありますが、今回はJDK 21、Ubuntu Linux 24.04 LTSの`Dockerfile`を参考にしたいと思います。

https://github.com/docker-library/tomcat/blob/master/10.1/jdk21/temurin-noble/Dockerfile

この中身を見ると、やっていることは以下ですね。

- curlやSSL証明書のインストール
- Apache Tomcatのダウンロード
- ダウンロードしたファイルの検証
- `.bat`ファイルの削除
- デフォルトの`webapps`ディレクトリの退避
  - https://tomcat.apache.org/tomcat-9.0-doc/security-howto.html#Default_web_applications
- Apache Tomcatのネイティブライブラリのビルドに必要なライブラリのインストール
- Apache Tomcatのネイティブライブラリのビルド
- shebangの`#!/bin/sh`を`#!/usr/bin/env bash`に書き換え
- `CMD ["catalina.sh", "run"]`で起動設定

ちなみに、`catalina.sh`を`run`で実行するとApache Tomcatがフォアグラウンドで起動します。

一方で、ベースにするのはAmazon CorrettoのDockerイメージです。

[dockerhub / amazoncorretto](https://hub.docker.com/_/amazoncorretto)

# 環境

今回の環境はこちらです。

```shell
$ docker version
Client: Docker Engine - Community
 Version:           28.0.0
 API version:       1.48
 Go version:        go1.23.6
 Git commit:        f9ced58
 Built:             Wed Feb 19 22:11:04 2025
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          28.0.0
  API version:      1.48 (minimum version 1.24)
  Go version:       go1.23.6
  Git commit:       af898ab
  Built:            Wed Feb 19 22:11:04 2025
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.7.25
  GitCommit:        bcc810d6b9066471b0b6fa75f557a15a1cbf31bb
 runc:
  Version:          1.2.4
  GitCommit:        v1.2.4-0-g6c52b3f
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0


$ java --version
openjdk 21.0.6 2025-01-21
OpenJDK Runtime Environment (build 21.0.6+7-Ubuntu-124.04.1)
OpenJDK 64-Bit Server VM (build 21.0.6+7-Ubuntu-124.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
Maven home: /home/charon/.sdkman/candidates/maven/current
Java version: 21.0.6, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "6.8.0-53-generic", arch: "amd64", family: "unix"
```

# Amazon Corretto 21をベースイメージにした、Apache Tomcat 10.1のDockerイメージを作る

それでは、Amazon Corretto 21をベースイメージにした、Apache Tomcat 10.1のDockerイメージを作っていきます。

今回は以下のことをやってみましょう。

- Amazon Corretto 21をベースイメージにした、Apache Tomcat 10.1のDockerイメージを作る
- 作ったDockerイメージに対して簡単なWebアプリケーション（Jakarta Servletのみ）をデプロイする
- 作ったDockerイメージをベースにJib Maven PluginでDockerイメージを作成し、簡単なWebアプリケーション（Jakarta Servletのみ）をデプロイする

## Dockerイメージを作る

まずはDockerイメージを作ります。

作成した`Dockerfile`はこちらです。

```dockerfile:Dockerfile
FROM amazoncorretto:21.0.6-al2023
# FROM amazoncorretto:21.0.6-al2023-headless

ENV CATALINA_HOME=/usr/local/tomcat
ENV PATH=$CATALINA_HOME/bin:$PATH

RUN mkdir -p "$CATALINA_HOME"

WORKDIR $CATALINA_HOME

ENV TOMCAT_MAJOR=10
ENV TOMCAT_VERSION=10.1.36

RUN dnf install -y \
        tar \
        gzip \
        findutils \
    && dnf autoremove -y \
    && dnf clean all \
    && rm -rf /var/cache/dnf \
    && curl -s -L https://dlcdn.apache.org/tomcat/tomcat-${TOMCAT_MAJOR}/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz -o tomcat.tar.gz \
    && tar xf tomcat.tar.gz --strip-components=1 \
    && rm bin/*.bat \
    && rm tomcat.tar.gz \
    && mv webapps webapps.dist \
    && mkdir webapps \
    && find bin -name '*.sh' -exec sed -ri 's|^#!/bin/sh$|#!/usr/bin/env bash|' '{}' + \
    && chmod -R +rX . \
    && chmod 1777 logs temp work

EXPOSE 8080

ENTRYPOINT []

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

ベースイメージはこちらにしましたが、お好みでheadlessでもいいと思います。

```dockerfile
FROM amazoncorretto:21.0.6-al2023
# FROM amazoncorretto:21.0.6-al2023-headless
```

あとは、オリジナルから以下の部分を引っ張ってきています。ネイティブライブラリは今回は端折りました。

- Apache Tomcatのダウンロード
- `.bat`ファイルの削除
- デフォルトの`webapps`ディレクトリの退避
  - https://tomcat.apache.org/tomcat-9.0-doc/security-howto.html#Default_web_applications
- shebangの`#!/bin/sh`を`#!/usr/bin/env bash`に書き換え
- `CMD ["catalina.sh", "run"]`で起動設定

ビルド。

```shell
$ docker image build -t charon/tomcat:10.1.36-corretto-21.0.6-al2023 .
```

起動。

```shell
$ docker container run -it --rm --name tomcat charon/tomcat:10.1.36-corretto-21.0.6-al2023
Using CATALINA_BASE:   /usr/local/tomcat
Using CATALINA_HOME:   /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME:        /usr/lib/jvm/java-21-amazon-corretto
Using CLASSPATH:       /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
Using CATALINA_OPTS:
22-Feb-2025 08:02:44.782 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name:   Apache Tomcat/10.1.36
22-Feb-2025 08:02:44.784 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built:          Feb 13 2025 13:13:30 UTC
22-Feb-2025 08:02:44.785 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 10.1.36.0
22-Feb-2025 08:02:44.785 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name:               Linux
22-Feb-2025 08:02:44.785 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version:            6.8.0-53-generic
22-Feb-2025 08:02:44.785 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture:          amd64
22-Feb-2025 08:02:44.785 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home:             /usr/lib/jvm/java-21-amazon-corretto.x86_64
22-Feb-2025 08:02:44.785 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version:           21.0.6+7-LTS
22-Feb-2025 08:02:44.785 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor:            Amazon.com Inc.
22-Feb-2025 08:02:44.785 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE:         /usr/local/tomcat
22-Feb-2025 08:02:44.786 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME:         /usr/local/tomcat
22-Feb-2025 08:02:44.797 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
22-Feb-2025 08:02:44.797 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
22-Feb-2025 08:02:44.797 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
22-Feb-2025 08:02:44.797 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.protocol.handler.pkgs=org.apache.catalina.webresources
22-Feb-2025 08:02:44.797 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dsun.io.useCanonCaches=false
22-Feb-2025 08:02:44.797 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dorg.apache.catalina.security.SecurityListener.UMASK=0027
22-Feb-2025 08:02:44.797 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMED
22-Feb-2025 08:02:44.798 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.io=ALL-UNNAMED
22-Feb-2025 08:02:44.798 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util=ALL-UNNAMED
22-Feb-2025 08:02:44.798 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.base/java.util.concurrent=ALL-UNNAMED
22-Feb-2025 08:02:44.798 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
22-Feb-2025 08:02:44.798 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/usr/local/tomcat
22-Feb-2025 08:02:44.798 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/usr/local/tomcat
22-Feb-2025 08:02:44.798 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
22-Feb-2025 08:02:44.804 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent The Apache Tomcat Native library which allows using OpenSSL was not found on the java.library.path: [/usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib]
22-Feb-2025 08:02:44.966 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
22-Feb-2025 08:02:44.978 INFO [main] org.apache.catalina.startup.Catalina.load Server initialization in [339] milliseconds
22-Feb-2025 08:02:45.003 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
22-Feb-2025 08:02:45.004 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/10.1.36]
22-Feb-2025 08:02:45.026 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
22-Feb-2025 08:02:45.032 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [53] milliseconds
```

OKですね。

サイズはこれくらいです。headlessにすると100Mくらい減ります。

```shell
$ docker image ls
REPOSITORY        TAG                              IMAGE ID       CREATED              SIZE
charon/tomcat     10.1.36-corretto-21.0.6-al2023   a06526270d06   4 seconds ago        512MB
```

## アプリケーションをデプロイしてみる

簡単なアプリケーションをJakarta Servletで作成してデプロイしてみましょう。

```xml:pom.xml
<?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>com.example</groupId>
    <artifactId>simple-servlet</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>6.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>ROOT</finalName>
    </build>
</project>
```

```java:src/main/java/com/example/SimpleServlet.java
package com.example;

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 SimpleServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String word = request.getParameter("word");

        response.getWriter().printf("Hello %s!!%n", word);
    }
}
```

パッケージング。

```shell
$ mvn package
```

作成したApache Tomcatのイメージにデプロイしてみます。

```shell
$ docker container run -it --rm --name tomcat -p 8080:8080 -v $(pwd)/target/ROOT.war:/usr/local/tomcat/webapps/ROOT.war charon/tomcat:10.1.36-corretto-21.0.6-al2023
```

確認。

```shell
$ curl 'localhost:8080?word=Tomcat'
Hello Tomcat!!
```

## Jib Maven Pluginで使ってみる

最後はJib Maven Pluginで使ってみます。

サンプルのJakarta Servletは先ほどのものと同じものを使います。

`pom.xml`にはJib Maven Pluginの設定を追加しました。

```xml
    <build>
        <finalName>ROOT</finalName>
        <plugins>
            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>3.4.4</version>
                <configuration>
                    <from>
                        <image>docker://charon/tomcat:10.1.36-corretto-21.0.6-al2023</image>
                    </from>
                    <to>
                        <image>charon/${project.artifactId}</image>
                    </to>
                    <container>
                        <appRoot>/usr/local/tomcat/webapps/ROOT</appRoot>
                    </container>
                </configuration>
            </plugin>
        </plugins>
    </build>
```

ベースイメージはDockerデーモンから取得します。

```xml
                    <from>
                        <image>docker://charon/tomcat:10.1.36-corretto-21.0.6-al2023</image>
                    </from>
```

ビルド。

```shell
$ mvn package jib:dockerBuild
```

できました。

```shell
$ docker image ls
REPOSITORY              TAG                              IMAGE ID       CREATED          SIZE
charon/tomcat           10.1.36-corretto-21.0.6-al2023   a06526270d06   21 minutes ago   512MB
charon/simple-servlet   latest                           fab577535542   55 years ago     512MB
```

起動。

```shell
$ docker container run -it --rm --name tomcat -p 8080:8080 charon/simple-servlet:latest
```

動作確認。

```shell
$ curl 'localhost:8080?word=Tomcat'
Hello Tomcat!!
```

よさそうですね。