Spring

Spring BootのDockerfileの書き方|マルチステージ・レイヤー展開・AOTキャッシュを4.1.1で検証

Spring BootのDockerfileの書き方|マルチステージ・レイヤー展開・AOTキャッシュを4.1.1で検証

Spring BootのDockerfileの古い例には、今は取得できない openjdk のタグを指定しているものが多く、そのまま写すと1行目の FROM で止まります。openjdk イメージのGA版タグが、Docker Hubから消えているためです。この記事では、Spring Boot 4.1.1(2026年10月時点の最新GA)とJava 25で、2026年10月12日に実際にビルドが通ったDockerfileだけを載せます。先に完成形を示し、続けて各行の理由と、MavenとGradleで分かれる箇所、本番で効く実行時の設定を説明します。

まとめ

  • ベースイメージ:openjdk:17-jdk-alpine や openjdk:21 はmanifestがHTTP 404を返します。eclipse-temurin:25-jre などへ置き換えます。
  • 骨格:ビルドステージ(Maven+JDK)と実行ステージ(JRE)に分けます。依存のダウンロードは RUN --mount=type=cache でビルドをまたいで残します。
  • レイヤー展開:java -Djarmode=tools -jar application.jar extract --layers でuber jarをほどきます。旧構文の layertools は4.1.0で削除済みで、4.1.1では Error: Unsupported jarmode 'layertools' になります。
  • 起動高速化:Java 25以降はAOTキャッシュです。後述の検証では起動が約4.1〜4.6秒から約1.8秒に縮みました。展開後のjarでないと効きません。
  • 実行時:UIDで非root化、-XX:MaxRAMPercentage でヒープの割合を指定(既定は25%)、ヘルスチェックはcurlに頼らない(eclipse-temurin のJREイメージにはcurlもwgetも入っていません)。

完成形のDockerfile(Maven・Java 25)

単一モジュールのMavenプロジェクトのルートに置く構成です。Spring Initializr で作ったSpring Boot 4.1.1の雛形(Spring Web+Actuator・Java 25)を、Docker 29.5.2とbuildx v0.38.0でビルドして動作を確かめています。

# syntax=docker/dockerfile:1
# ---- ビルドステージ ----
FROM maven:3.9-eclipse-temurin-25 AS build
WORKDIR /build
COPY pom.xml .
RUN --mount=type=cache,target=/root/.m2 mvn -B -q dependency:go-offline
COPY src ./src
RUN --mount=type=cache,target=/root/.m2 mvn -B -q -DskipTests package \
 && cp target/*.jar application.jar \
 && java -Djarmode=tools -jar application.jar extract --layers --destination extracted

# ---- 実行ステージ ----
FROM eclipse-temurin:25-jre
RUN groupadd --system --gid 10001 spring \
 && useradd --system --uid 10001 --gid 10001 --create-home spring
WORKDIR /application
COPY --from=build /build/extracted/dependencies/ ./
COPY --from=build /build/extracted/spring-boot-loader/ ./
COPY --from=build /build/extracted/snapshot-dependencies/ ./
COPY --from=build /build/extracted/application/ ./
# AOTキャッシュの訓練実行(Java 25以降)
RUN java -XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh -jar application.jar
USER 10001:10001
ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75"
EXPOSE 8080
ENTRYPOINT ["java", "-XX:AOTCache=app.aot", "-jar", "application.jar"]
docker build -t demo:0.1.0 .
docker run --rm -p 8080:8080 -m 1g demo:0.1.0

同じ条件(colima上のx86_64 VM、-m 1g によるメモリ上限1GiB)で3回ずつ起動したときの値です。この雛形での値なので、依存の多いアプリでは起動時間も短縮幅も変わります。

項目 値
起動時間(AOTキャッシュあり) 1.791〜1.831秒
起動時間(同じイメージでキャッシュ指定なし) 4.113〜4.611秒
dependencies層 21.9MB
application層 12.3kB
AOTキャッシュ(app.aot)層 57.3MB

起動時間はログの Started DemoApplication in ... seconds の値です。アプリのコードを1行直して再ビルドすると、依存の層の COPY はキャッシュ扱い(CACHED)になり、作り直されたのは application 層と訓練実行の層だけでした。以下、上から分解して説明します。

openjdkのGA版タグ削除とビルド失敗

Docker Hubのopenjdkリポジトリには「This image is officially deprecated and all users are recommended to find and use suitable replacements ASAP.」という告知があり、「The only tags which will continue to receive updates beyond July 2022 will be Early Access builds」と続きます。リポジトリ自体は更新が続いているので、Docker Hubを開くと現役に見えます。しかし残っているのはEarly Accessのタグだけで、旧記事が使っていたGA版のタグはmanifestを返しません。

イメージ:タグ manifest応答 中身
openjdk:17-jdk-alpine 404 なし
openjdk:21 404 なし
openjdk:27-ea 200 27-ea+34
openjdk:28-ea-slim 200 28-ea+18
eclipse-temurin:25-jre 200 jdk-25.0.4.1+1

2026年10月12日にDocker Registry API(registry v2)へ問い合わせた結果です。FROM openjdk:17-jdk-alpine はpullに失敗してビルドが止まります。次のコマンドでHTTPステータスを確かめられます。

TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/openjdk:pull" \
  | python3 -c "import json,sys;print(json.load(sys.stdin)['token'])")

curl -s -o /dev/null -w "%{http_code}\n" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.oci.image.index.v1+json" \
  https://registry-1.docker.io/v2/library/openjdk/manifests/17-jdk-alpine

Early Accessタグは開発中のJDKなので、本番の置き換え先にはなりません。告知は代替の例として amazoncorretto、eclipse-temurin、ibm-semeru-runtimes、ibmjava、sapmachine を挙げています。ディストリビューションの比較と配布手順はSpring BootアプリのDockerデプロイ手順にまとめてあります。

ベースイメージの選定基準

eclipse-temurinとLibericaの違い

この記事は eclipse-temurin を使い、Spring Boot公式ドキュメントのDockerfile例は bellsoft/liberica-openjre-debian:25-cds を使っています。後者はDocker Official Imageの枠外なので、「公式イメージのみ」という社内ルールがあると公式例をそのまま写せません。対応CPUにも差があり、Apple SiliconのMacとx86_64のサーバーで同じイメージを使うなら、両方のアーキテクチャがあるかを先に見ます(registry v2のマニフェストで確認)。

イメージ:タグ 圧縮サイズ(amd64) 対応アーキテクチャ
eclipse-temurin:25-jre 120.1MB amd64・arm64・ppc64le・s390x
eclipse-temurin:25-jdk 153.6MB amd64・arm64・ppc64le・s390x
bellsoft/liberica-openjre-debian:25-cds 128.1MB amd64・arm64
maven:3.9-eclipse-temurin-25 176.3MB amd64・arm64・ppc64le・riscv64・s390x

複数アーキテクチャのイメージをCIで作る設定はGitHub ActionsでDockerイメージをビルドするで扱っています。

ENTRYPOINT上書き時のCA証明書取り込み

eclipse-temurin の既定 Entrypoint は ["/__cacert_entrypoint.sh"] で、コンテナ内の /certificates に置いたCA証明書を、環境変数 USE_SYSTEM_CA_CERTS が設定されているときにtruststoreへ取り込むラッパーです(Docker Hubのeclipse-temurinの説明)。自前の ENTRYPOINT はこれを上書きするので、この仕組みで社内CAを入れる環境では ENTRYPOINT ["/__cacert_entrypoint.sh", "java", "-jar", "application.jar"] のように前段を残します。

イメージタグの固定と更新管理

25-jre のようなメジャー版タグは中身が入れ替わります。2026年8月19日の 25-jre は JAVA_VERSION=jdk-25.0.3+9 でしたが、10月12日には jdk-25.0.4.1+1 になり、圧縮サイズも111.4MBから120.1MBに増えました。再現性が要るなら eclipse-temurin:25.0.4.1_1-jre のようにパッチ版まで、監査対象ならダイジェストまで固定します。ただし8月に固定した 25.0.3_9-jre は今もpullできる代わりに、10月時点では2世代前のJVMです。Renovateなどの更新ボットか月1回の見直しとセットにしないと、固定は古いJVMを使い続ける理由になります。

マルチステージによるビルド環境と実行環境の分離

jarコピーの最小構成とマルチステージ構成

ビルド済みのjarが target/ にあれば、4行で動きます。

FROM eclipse-temurin:25-jre
WORKDIR /app
COPY target/*.jar application.jar
ENTRYPOINT ["java", "-jar", "application.jar"]

ただしjarのビルドが手元の環境に依存します。マルチステージ構成にすると、Mavenとソースはビルドステージに閉じ込め、実行ステージにはjarだけを渡せます。

FROM maven:3.9-eclipse-temurin-25 AS build
WORKDIR /build
COPY pom.xml .
RUN mvn -B dependency:go-offline
COPY src ./src
RUN mvn -B -DskipTests package

FROM eclipse-temurin:25-jre
WORKDIR /app
COPY --from=build /build/target/*.jar application.jar
ENTRYPOINT ["java", "-jar", "application.jar"]

実行ステージをJREにするだけで、ベースの圧縮サイズは maven:3.9-eclipse-temurin-25 の176.3MBから eclipse-temurin:25-jre の120.1MBへ下がり、Mavenのローカルリポジトリとソースツリーも最終イメージに残りません。Spring Boot 4.1.1のシステム要件はJava 17以上26以下、Maven 3.6.3以上、Gradleは8.14以上の8.x系か9.x系です。ベースイメージのJavaは、この公式の対応範囲と、pom.xml の java.version の両方を満たすものを選びます。Spring Bootの各版のサポート期限はSpring Bootバージョン一覧とサポート期限で確認できます。

Maven依存のキャッシュマウント

COPY pom.xml と COPY src を分けているのは、Dockerが命令単位でキャッシュを使うからです。ソースだけを直した再ビルドでは mvn dependency:go-offline がキャッシュから復元されます。COPY . . の1行にすると、ソースを触るたびに依存を取り直します。ただしこの方式でも、pom.xml を1行変えれば全依存の再ダウンロードになります。そこで完成形では、BuildKitのキャッシュマウント(RUN --mount=type=cache,target=/root/.m2)でMavenのローカルリポジトリをビルドの外に置いています。Dockerのドキュメントはこれを「persistent package cache」と呼び、レイヤーを作り直しても新しいパッケージだけを取りに行くと説明しています。キャッシュの中身はイメージに入りません。

BuildKitが動いていないと、ここで止まります。buildxプラグインが無いDocker 29.5.2は旧ビルダーで動き、the --mount option requires BuildKit で失敗しました。colimaやLinuxで素のDocker Engineを入れた環境では docker buildx version を先に確認します。BuildKitの仕組みとキャッシュの種類はBuildKitとは、Macでの環境構築はColimaとは?MacでDocker Desktopを外す導入手順と設定の落とし穴で扱っています。

.dockerignoreによるビルドコンテキストの除外

ルートの .dockerignore にマッチしたファイルは、ビルダーへ送られる前に除外されます。ビルドをコンテナ内で行う完成形なら、手元のビルド成果物も除外できます。

.git/
.idea/
.vscode/
target/
build/
.gradle/
*.iml

手元のjarを COPY target/*.jar する最小構成やデバッグ用では、このCOPYが失敗します。その場合は Dockerfile.debug.dockerignore のようにDockerfile名を前置した除外ファイルを置くと、ルートの .dockerignore より優先されます。命令そのものの意味はDockerfileとは|書き方・主要命令・ベストプラクティスを解説で個別に解説しています。

Gradleで書くときの違い

Spring Boot公式のDockerfile例には「Adjust this to ‘build/libs/*.jar’ if you’re using Gradle」というコメントがあります。ところが、Gradleで ./gradlew build を実行すると build/libs にjarが2本並びます。

$ ./gradlew build
$ ls build/libs
demo-0.0.1-SNAPSHOT-plain.jar
demo-0.0.1-SNAPSHOT.jar

-plain.jar は依存を含まない通常のjarです。Spring BootのGradleプラグインのドキュメントによると、bootJar タスクを設定すると jar タスクの分類子が plain になり、実行可能jarと通常のjarが同時に作られます。この状態で COPY build/libs/*.jar application.jar と書くと、ビルダーによって結果が変わります。

  • 旧ビルダー(DOCKER_BUILDKIT=0)は、Dockerfileリファレンスの「複数のソースを指定したらコピー先はスラッシュで終わるディレクトリでなければならない」という規則どおりエラーで止まります。
  • buildx v0.38.0のBuildKitではエラーにならず、2本が同じファイル名へ順に上書きされました。名前順で -plain.jar が先、実行可能jarが後に来るため実行可能jarが残り、起動もします。仕様として保証された動作ではありません。
Step 3/4 : COPY build/libs/*.jar application.jar
When using COPY with more than one source file, the destination must be a directory and end with a /

BuildKitで動いているのは並び順のおかげにすぎません。tasks.named("jar") { enabled = false } で通常のjarを作らないようにするか、Dockerfile側で bootJar タスクだけを実行します。

// build.gradle(Groovy DSL)
tasks.named("jar") {
    enabled = false
}

ビルドステージをGradleで書くと次の形です。gradle:9-jdk25 イメージはrootで動き GRADLE_USER_HOME を設定していないので、キャッシュの置き場は /root/.gradle になります。実行ステージは完成形と同じです。

FROM gradle:9-jdk25 AS build
WORKDIR /build
COPY settings.gradle build.gradle ./
COPY src ./src
RUN --mount=type=cache,target=/root/.gradle gradle bootJar --no-daemon \
 && cp build/libs/*.jar application.jar \
 && java -Djarmode=tools -jar application.jar extract --layers --destination extracted

jarmode=toolsによるレイヤー展開

extractが作る4つのディレクトリ

Spring Bootのuber jarは依存ライブラリとアプリのクラスを1ファイルに詰めています。そのままCOPYすると全体が1レイヤーになるため、Spring Bootはjarをほどくモードを持っています。

$ java -Djarmode=tools -jar application.jar

Usage:
  java -Djarmode=tools -jar application.jar

Available commands:
  extract      Extract the contents from the jar
  list-layers  List layers from the jar that can be extracted
  help         Help about any command

extract --layers は dependencies・spring-boot-loader・snapshot-dependencies・application の4ディレクトリを作ります。変更頻度の低い順に別々の COPY で積むと、アプリだけを直したときに作り直される層を application 層に限定できます(AOTキャッシュを足した完成形では、訓練実行の層も作り直しと転送の対象になります)。先の実測では依存の層21.9MBに対してアプリの層は12.3kBでした。この雛形では spring-boot-loader と snapshot-dependencies は空です(SNAPSHOT版の依存が無いため)。

展開後のjar名は元のファイル名のまま

公式例が COPY ${JAR_FILE} application.jar でいったん名前を変えてから展開しているのには理由があります。extract が作る起動用jarは、展開元のjarと同じファイル名になります。完成形の cp target/*.jar application.jar を省くと起動用jarは demo-0.0.1-SNAPSHOT.jar になり、訓練実行の RUN java ... -jar application.jar が Error: Unable to access jarfile application.jar で失敗しました。AOTキャッシュのドキュメントも、展開後に元の名前の my-app.jar を起動する例になっています。ビルドとレイヤー展開を1つのステージにまとめるときは、展開前に名前をそろえておきます。

Spring Boot 4.1.0でのlayertools削除と移行先

ネット上の例には -Djarmode=layertools がまだ多く残っています。版によって動いたり止まったりします。

Spring Bootの版 jarmode=layertools jarmode=tools
2.3.0〜3.2.x 使用可 未提供
3.3.0〜4.0.x(2026年10月時点は4.0.8) 使用可(互換のため残置) 使用可
4.1.0以降 削除済み 使用可

境目はソースで確定できます。spring-boot-jarmode-tools の META-INF/spring.factories を見ると、v4.0.8は LayerToolsJarMode と ToolsJarMode の2つを登録し、v4.1.1は ToolsJarMode だけです。4.1.1のjarで旧構文を実行した結果が次の出力です。

$ java -Djarmode=layertools -jar demo-0.0.1-SNAPSHOT.jar list
Error: Unsupported jarmode 'layertools'

3.5系や4.0系のうちは旧構文でも動くので気付きにくく、4.1へ上げた時点でイメージのビルドが落ちます。上げる前にリポジトリ全体へ grep -r layertools をかけておくと安全です。

AOTキャッシュによる起動時間の短縮

Java 25以降の訓練実行

Spring Bootのドキュメントは「Spring Boot supports the AOT cache for Java 25 and above.」としています。訓練実行(training run)でクラスのロードやリンクの結果をファイルに保存し、本番起動でそれを読み込む仕組みです。

# 訓練実行:起動して初期化が終わった時点で終了する
RUN java -XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh -jar application.jar

# 本番起動:焼いたキャッシュを読ませる
ENTRYPOINT ["java", "-XX:AOTCache=app.aot", "-jar", "application.jar"]

-Dspring.context.exit=onRefresh はアプリケーションコンテキストのリフレッシュが終わった時点でプロセスを終わらせる指定です。これが無いと訓練実行がサーバーとして起動したままになり、ビルドが終わりません。訓練実行中には [warning][aot] class ... cannot be archived という警告が数行出ますが、一部のクラスがキャッシュに入らなかったという通知で、ビルドは成功します。

ドキュメントには「You have to use the cache file with the extracted form of the application, otherwise it has no effect.」とあり、展開していないuber jarに指定してもエラーにならないまま効果がゼロになります。キャッシュが使われているかは起動ログから判別しにくいので、JEP 483が示す -XX:AOTMode=on を付けると、キャッシュを使えないときにJVMがエラーで終了します。完成形のイメージではこの確認が終了コード0で通り、存在しないファイルを指定すると An error has occurred while processing the AOT cache で止まりました。

docker run --rm --entrypoint java demo:0.1.0 \
  -XX:AOTCache=app.aot -XX:AOTMode=on -Dspring.context.exit=onRefresh -jar application.jar

キャッシュを再利用できるのは「アプリを更新せず、同じJavaのバージョンを使う限り」です。訓練実行をDockerfileの中に置けば、この2条件をビルドのたびに満たせます。ただし訓練実行は docker build の中でアプリを起動するので、初期化中にDBなど外部サービスへ接続するアプリは、接続先が無くて失敗することがあります。Spring BootのAOTキャッシュのhow-toは、訓練実行のときだけ外部への接続を止める設定を入れるよう案内しています。

Java 17・21でのCDS設定

Java 17や21で運用しているなら、AOTキャッシュの前身であるCDS(Class Data Sharing)を使います。手順は同じで、フラグと出力ファイル名が変わるだけです。

RUN java -XX:ArchiveClassesAtExit=application.jsa -Dspring.context.exit=onRefresh -jar application.jar
ENTRYPOINT ["java", "-XX:SharedArchiveFile=application.jsa", "-jar", "application.jar"]

なお公式ドキュメントは、AOTキャッシュのページでは「Java 25 or above」、DockerfileのページのCDS節では「If you’re using Java 24 or later, please use AOT cache instead of CDS.」と、異なる境界を書いています。サポート範囲を明示しているのは前者なので、本記事は25で区切っています。

本番で効く実行時の設定

非root実行とexec形式のENTRYPOINT

USER を指定しなければ、コンテナのプロセスはrootで動きます。eclipse-temurin 側もユーザーを設定していないので、明示的に落とします。groupadd を先に実行しているのは、useradd --system がGIDを自動で割り当て、--uid 10001 だけではGIDが10001になる保証が無いためです。USER を数値のUIDで書くと、Kubernetesの runAsNonRoot がユーザー名を解決できずに起動を拒否する問題も避けられます。

アプリのファイルにはあえて --chown を付けていません。所有者はrootのままでプロセスはUID 10001なので、アプリは自分のjarやAOTキャッシュを読めても書き換えられません。USER を訓練実行の後ろに置くのは、app.aot の書き出しをroot権限で行うためです。

ENTRYPOINT はJSON配列(exec形式)で書きます。Dockerfileリファレンスによると、シェル形式では /bin/sh -c の子プロセスとして起動し、javaがPID 1にならず docker stop のSIGTERMを受け取りません。SIGTERMが届かなければSpring Bootのグレースフルシャットダウンは始まらず、猶予時間のあとSIGKILLで強制終了されます。停止順序の設計はグレースフルシャットダウンとは?SIGTERM受信後の停止順序と猶予時間の設計で扱っています。

コンテナのメモリ上限とJVMヒープ比率

JVMはコンテナのメモリ上限を認識してヒープの上限を決めます。JDK 25のjavaコマンドのマニュアルにある通り、-XX:MaxRAMPercentage の既定値は25%です。-m 512m(512MiB)のコンテナでは、既定の最大ヒープはその4分の1の128MiBです。

docker run --rm -m 512m --entrypoint java eclipse-temurin:25-jre \
  -XX:+PrintFlagsFinal -version | grep " MaxHeapSize "
コンテナの上限 既定(25%) MaxRAMPercentage=75
512MiB 128MiB 384MiB
2GiB 512MiB 1,536MiB

完成形では ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75" とし、docker run -e やKubernetesのマニフェストから値を変えられるようにしています。起動時に Picked up JAVA_TOOL_OPTIONS: ... と1行出るのは正常です。残りはメタスペースやスレッドスタックなどヒープ以外に使われるので、75%から上げすぎるとコンテナごとOOMで落ちます。GCの選び方とログの見方はJavaのガベージコレクション(GC)とはにまとめています。

curl未導入イメージでのヘルスチェック

Dockerfileに HEALTHCHECK CMD curl -f http://localhost:8080/actuator/health と書く例をよく見ますが、eclipse-temurin:25-jre ではこれが動きません。イメージ内にcurl・wget・ncのいずれも存在しないためです。Adoptiumのイメージ定義では、JDKのダウンロードに使ったwgetを同じ RUN の中で削除しています。

Kubernetesなら、Actuatorのプローブ用エンドポイントをマニフェストに書きます。Spring Bootのドキュメントは /actuator/health/liveness と /actuator/health/readiness を挙げています。

livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080

ComposeでHEALTHCHECKが要るなら、実行ステージに apt-get install curl を足すか、既存のJREで動くチェック用クラスを同梱するかを、追加パッケージの更新管理とチェック処理の保守のどちらを引き受けるかで選びます。エンドポイントの公開範囲の設定はSpring Boot Actuatorの設定|exposure.includeとaccessの使い分けを参照してください。

コンテナ内のSpring Bootへのデバッガ接続

JDWPポートを開くDockerfileとCompose

JVMでJDWPエージェントを有効にしてポートを外へ出します。本番用とは別に開発用のDockerfileを作ります。

# Dockerfile.debug(開発用・本番には使わない)
FROM eclipse-temurin:25-jre
WORKDIR /app
COPY target/*.jar application.jar
EXPOSE 8080 5005
ENTRYPOINT ["java", \
  "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", \
  "-jar", "application.jar"]

要点は address の書き方です。JPDAの接続仕様では「If <host> is empty, the local loopback address is used.」とされ、address=5005 だけだとコンテナ内のループバックで待ち受けるため、ホスト側のIDEから接続できません。ホスト部を * にするとワイルドカードアドレスで待ち受けます。suspend=n はデバッガの接続を待たずに起動する指定です。

Compose側はデバッグポートを足すだけです。トップレベルの version は書きません。Composeのドキュメントは「It is only informative and you’ll receive a warning message that it is obsolete if used.」としています。

name: springboot-debug
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.debug
    ports:
      - "8080:8080"
      - "127.0.0.1:5005:5005"

公開先を 127.0.0.1 に限定しているのは、JDWPに認証が無いからです。"5005:5005" と書いて全インターフェースに開くと、同じネットワークの誰でも任意のコードを実行できます。

VS Codeのリモートアタッチ設定

VS CodeではExtension Pack for Javaに含まれるDebugger for Javaを使い、.vscode/launch.json にアタッチ構成を足します。

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "java",
      "name": "Attach to Docker",
      "request": "attach",
      "hostName": "localhost",
      "port": 5005,
      "projectName": "demo"
    }
  ]
}

VS CodeのJavaデバッグのドキュメントでは、projectName はワークスペースに複数のJavaプロジェクトがある場合に必須で、欠くと式の評価と条件付きブレークポイントが動かないことがあるとされています。エディタごとコンテナに寄せたいならDev Container(devcontainer)のメリットとはの構成のほうが素直です。

Dockerfileを書かない選択

イメージの中身に要件が無いなら、Dockerfileを書かないほうが手間は少なく済みます。Spring Bootのビルドプラグインは、Cloud Native Buildpacks(CNB)でjarから直接OCIイメージを作れます。

mvn spring-boot:build-image

Mavenプラグインのドキュメントによると、既定のビルダーは paketobuildpacks/builder-noble-java-tiny:latest で、「For security reasons, images build and run as non-root users.」とある通り非rootで動きます。初回はビルダーイメージのpullが入るので、手書きの docker build より時間がかかることがあります。AOTキャッシュも、Java 25以降でビルド時の環境変数 BP_JVM_AOTCACHE_ENABLED=true を渡せばbuildpackが訓練実行まで行います。

手書きのDockerfileが必要になるのは、CNB非互換のベースイメージ(distrolessなど)に固定したい場合や、ネイティブライブラリやフォントを足す場合のように、イメージの構成を直接決めたいときです。なお build-image は「requires access to a Docker daemon」と明記されています。Dockerfileのビルドにもビルダーは要るので、デーモンを置けないCIでは、独立したBuildKitを用意するか、レジストリへ直接出力するJib(jib-maven-plugin、2026年10月時点の最新は3.5.2)を選びます。JibはSpring Bootのレイヤー定義とは別のレイヤー分けになるため、本記事のレイヤー展開とは併用しません。作ったイメージをECSやEKSなどの本番基盤へ載せる設計は、一創のインフラ構築(AWS・Google Cloud・Azure)で支援しています。コンテナの起動・停止コマンドの使い分けはDockerコンテナ起動コマンドの使い分け、コンテナの隔離の仕組みはDockerの仕組みを図解が参考になります。

よくある質問

Spring BootのDockerfileでopenjdkイメージが使えなくなったのはなぜですか?

Docker Hubの openjdk が公式に非推奨となり、GA版タグのmanifestが削除されたためです。2026年10月12日時点で openjdk:17-jdk-alpine や openjdk:21 はHTTP 404を返し、応答するのは 27-ea のようなEarly Accessタグだけです。eclipse-temurin などへ置き換えます。

jarmodeのlayertoolsとtoolsはどう違いますか?

tools が現行の構文で、layertools は旧構文です。tools はSpring Boot 3.3.0で導入され、互換のために残っていた layertools は4.1.0で削除されました。4.1.1で旧構文を実行すると Error: Unsupported jarmode 'layertools' で止まります。展開コマンドは java -Djarmode=tools -jar application.jar extract --layers です。

Spring Bootをマルチステージビルドにすると何が速くなりますか?

マルチステージ化は実行イメージからビルドツールを外す手段で、アプリの実行速度は変わりません。実行ステージからJDKとMavenが消えるため、ベースだけで176.3MBが120.1MBになり、転送量が減ります。ビルド時間はキャッシュの置き方で決まります。起動を速くしたい場合は、AOTキャッシュ(Java 25以降)かCDSを別に組み込みます。

Dockerfileを書かずにSpring Bootのイメージを作れますか?

作れます。mvn spring-boot:build-image(Gradleは bootBuildImage)でCloud Native Buildpacksがjarからイメージを作り、既定のビルダーは paketobuildpacks/builder-noble-java-tiny:latest です。生成されるイメージは非rootで動きます。Dockerデーモンへのアクセスが必要なので、デーモンを使えないCIでは、レジストリへ直接出力するJibか、独立したBuildKitでDockerfileをビルドする構成を選びます。

コンテナの中で動くSpring Bootにデバッガをつなぐには何が必要ですか?

JVMの起動オプションに -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 を加え、ポート5005をコンテナの外へ公開します。address のホスト部を省くとループバックだけで待ち受けるため、* を明示します。JDWPは無認証なので、公開先は 127.0.0.1:5005:5005 のようにローカルに限定してください。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.16 テックブログ バーガーキング不正アクセスで約1,118万件の情報漏洩|対象確認・案内メールの真偽と運営側のログ点検
  2. 2026.10.16 テックブログ ソースネクスト不正アクセスで最大2,252万件流出|ログインできない理由・パスワード再設定と会員APIの点検
  3. 2026.10.15 テックブログ リゾートトラスト不正アクセス:会員2.6万件・カハラ横浜3.6万件の対象確認と窓口
  4. 2026.10.15 テックブログ 銀のさら不正アクセス:最大650万件の対象確認と窓口、パスワード変更の手順
  5. 2026.10.09 テックブログ 京王電鉄のランサムウェア被害とグループ共通基盤:決済・ポイント・予約が止まった範囲と遮断の初動

RELATED POSTS 関連記事

目次