Spring Boot の Dockerfile とは、実行可能 JAR を jarmode=tools でレイヤーごとに展開し、変わりにくい順にコピーして JRE のイメージで起動する手順を書いたファイルです。 JAR を1つコピーするだけでも動きますが、展開してレイヤーを分けると、コードを直したときに送り直す量が小さくなり、AOT キャッシュ(Java 25 以降)や CDS も使えるようになります。

この記事では、公式リファレンス(4.1.1)の Dockerfile を土台に、ビルド段・非 root・JVM メモリ設定・HEALTHCHECK まで入れた Maven 用と Gradle 用の全文、.dockerignore、DB 付きの compose.yaml を載せます。方式の比較表、Java の版ごとの起動オプション、4行の Dockerfile を段階的に直していく例もまとめました。

確認環境:Spring Boot 4.1.1 の公式リファレンスと Docker 公式ドキュメントで確認(確認日 2026年10月7日)。Java 単体で再現できる部分(JVM オプション、CDS、ヘルスチェック用クラス)は Ubuntu 24.04・OpenJDK 21.0.10 で実行。compose.yaml は Docker Compose v5.1.3 の docker compose config で構文を確認しています。

Spring Boot Dockerfileの早見表

方式 必要なもの 再ビルドで変わる層 向く場面
JAR をそのまま COPY 作成済みの JAR JAR 全体(依存込み) 手元で試す
マルチステージ ソースと Dockerfile JAR 全体(依存込み) ビルド環境をそろえたい
レイヤー展開 jarmode=tools application 層が中心 継続的に配布する
AOT キャッシュ・CDS 展開済みの構成と学習実行 application 層とキャッシュ 起動を速くしたい
Buildpacks 動作中の Docker デーモン ビルドパックが管理 Dockerfile を持たない運用

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Spring Boot Dockerfile|コピペ用全文とAOTキャッシュ設定」

コピペ用のDockerfile全文(Maven)

公式の Dockerfile(展開段と実行段)の前にビルド段を足し、非 root・JVM オプション・HEALTHCHECK を加えたものです。プロジェクト直下に Dockerfile として置きます。前提は、Maven Wrapper(mvnw と .mvn/)があること、spring-boot-starter-actuator を入れていること、ポートが 8080 であることです。

# ---- 1) ビルド段:JDK で実行可能 JAR を作る ----
FROM bellsoft/liberica-openjdk-debian:25 AS build
WORKDIR /workspace

# 依存関係の定義だけを先にコピーし、依存の取得をレイヤーとして残す
COPY mvnw pom.xml ./
COPY .mvn/ .mvn/
RUN ./mvnw -B dependency:go-offline

# ソースをコピーして JAR を作る(テストは CI 側で実行する前提)
COPY src/ src/
RUN ./mvnw -B package -Dmaven.test.skip=true

# HEALTHCHECK 用の小さなクラスをコンパイルする
COPY docker/HealthCheck.java /tmp/HealthCheck.java
RUN javac -d /workspace/health /tmp/HealthCheck.java

# ---- 2) 展開段:JAR をレイヤーごとのフォルダに展開する ----
FROM bellsoft/liberica-openjre-debian:25-cds AS builder
WORKDIR /builder
COPY --from=build /workspace/target/*.jar application.jar
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted

# ---- 3) 実行段:JRE だけの小さなイメージ ----
FROM bellsoft/liberica-openjre-debian:25-cds
WORKDIR /application

# 変わりにくい順にコピーする(COPY 1回が1レイヤーになる)
COPY --from=builder /builder/extracted/dependencies/ ./
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
COPY --from=builder /builder/extracted/application/ ./
COPY --from=build /workspace/health/ ./health/

# コンテナのメモリ上限に対するヒープの割合などをまとめて指定する
ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75 -XX:+ExitOnOutOfMemoryError"

# root 以外のユーザーで動かす
USER 10001:10001
EXPOSE 8080

# readiness が UP を返すかを 30 秒ごとに確認する
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
  CMD ["java", "-cp", "/application/health", "HealthCheck", "http://localhost:8080/actuator/health/readiness"]

# exec 形式で起動する(展開後の application.jar は、アプリのクラスと lib/ への参照だけを持つ)
ENTRYPOINT ["java", "-jar", "application.jar"]

イメージの作成と起動は次のとおりです。

docker build -t myapp:1.0.0 .
docker run --rm -p 8080:8080 -m 512m myapp:1.0.0

各ブロックの役割を表にまとめます。

ブロック していること 理由
ビルド段 JDK のイメージで ./mvnw package 手元の JDK の違いに左右されない
dependency:go-offline 依存関係(プラグイン含む)を先に解決 pom.xml が同じなら層を使い回せる
展開段 extract --layers --destination extracted JAR を4つのフォルダに分ける
COPY 4回 変わりにくい順にコピー コード変更時に変わる層を小さくする
ENV JAVA_TOOL_OPTIONS ヒープの割合などを指定 コンテナの上限に合わせる
USER 10001:10001 root 以外で実行 権限を必要な分だけにする
HEALTHCHECK readiness を定期確認 応答できる状態かを Docker が把握できる
ENTRYPOINT(exec 形式) java を直接起動 停止シグナルが Java に直接届く

ベースイメージは、公式リファレンスが例に使っている bellsoft/liberica-openjre-debian:25-cds(実行用)と、同じ提供元の bellsoft/liberica-openjdk-debian:25(ビルド用)です。どちらのタグも Docker Hub で公開されていることを確認しています(確認日 2026年10月7日)。別のディストリビューションを使う場合は、FROM の3行を置き換えます。

HEALTHCHECK用のクラス

実行用の JRE イメージに HTTP クライアントのコマンドが入っているとは限らないため、Java 自身でヘルスチェックする小さなクラスを使います。docker/HealthCheck.java として置いてください。

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

// HEALTHCHECK 用:URL に GET して 200 かつ本文に "UP" を含めば 0、それ以外は 1 で終了する
public class HealthCheck {
    public static void main(String[] args) {
        String url = args.length > 0 ? args[0] : "http://localhost:8080/actuator/health/readiness";
        try {
            HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(2)).build();
            HttpRequest request = HttpRequest.newBuilder(URI.create(url)).timeout(Duration.ofSeconds(3)).GET().build();
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            boolean up = response.statusCode() == 200 && response.body().contains("\"UP\"");
            System.out.println(response.statusCode() + " " + response.body());
            System.exit(up ? 0 : 1);
        } catch (Exception e) {
            System.out.println("NG " + e.getClass().getSimpleName());
            System.exit(1);
        }
    }
}

OpenJDK 21.0.10 でコンパイルし、{"status":"UP"} を返す URL、503 を返す URL、停止中のポートの順に実行した結果です。

$ javac -d out HealthCheck.java
$ java -cp out HealthCheck http://127.0.0.1:10400/actuator/health/readiness; echo "exit=$?"
200 {"status":"UP"}
exit=0
$ java -cp out HealthCheck http://127.0.0.1:10400/actuator/health/down; echo "exit=$?"
503 {"status":"OUT_OF_SERVICE"}
exit=1
$ java -cp out HealthCheck http://127.0.0.1:10400/actuator/health/readiness; echo "exit=$?"
NG ConnectException
exit=1

Docker の HEALTHCHECK は、コマンドの終了コード 0 を「正常」、1 を「異常」として扱います(2 は予約)。オプションの既定値は次のとおりです。

オプション 既定値 この記事の値 意味
--interval 30s 30s 確認の間隔
--timeout 30s 5s 1回の確認の制限時間
--start-period 0s 40s 起動直後の猶予
--retries 3 3 異常と判定するまでの連続回数

確認先の /actuator/health/readiness は、Spring Boot 4.0 から liveness/readiness のプローブが既定で有効になったため、追加の設定なしで使えます。3 系では management.endpoint.health.probes.enabled=true を設定して有効にします。Actuator が HTTP に公開するエンドポイントは既定で health だけなので、ヘルスチェックのために公開範囲を広げる必要はありません。

コピペ用のDockerfile全文(Gradle)

Gradle Wrapper(gradlew と gradle/)を使う場合の全文です。Maven 用との違いは、ビルド段の4行と JAR の場所だけです。

# ---- 1) ビルド段:JDK で実行可能 JAR を作る ----
FROM bellsoft/liberica-openjdk-debian:25 AS build
WORKDIR /workspace

# 依存関係の定義だけを先にコピーし、依存の取得をレイヤーとして残す
COPY gradlew settings.gradle build.gradle ./
COPY gradle/ gradle/
RUN ./gradlew --no-daemon dependencies

# ソースをコピーして JAR を作る(テストは CI 側で実行する前提)
COPY src/ src/
RUN ./gradlew --no-daemon bootJar

# HEALTHCHECK 用の小さなクラスをコンパイルする
COPY docker/HealthCheck.java /tmp/HealthCheck.java
RUN javac -d /workspace/health /tmp/HealthCheck.java

# ---- 2) 展開段:JAR をレイヤーごとのフォルダに展開する ----
FROM bellsoft/liberica-openjre-debian:25-cds AS builder
WORKDIR /builder
COPY --from=build /workspace/build/libs/*.jar application.jar
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted

# ---- 3) 実行段:JRE だけの小さなイメージ ----
FROM bellsoft/liberica-openjre-debian:25-cds
WORKDIR /application

# 変わりにくい順にコピーする(COPY 1回が1レイヤーになる)
COPY --from=builder /builder/extracted/dependencies/ ./
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
COPY --from=builder /builder/extracted/application/ ./
COPY --from=build /workspace/health/ ./health/

# コンテナのメモリ上限に対するヒープの割合などをまとめて指定する
ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75 -XX:+ExitOnOutOfMemoryError"

# root 以外のユーザーで動かす
USER 10001:10001
EXPOSE 8080

# readiness が UP を返すかを 30 秒ごとに確認する
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
  CMD ["java", "-cp", "/application/health", "HealthCheck", "http://localhost:8080/actuator/health/readiness"]

# exec 形式で起動する(展開後の application.jar は、アプリのクラスと lib/ への参照だけを持つ)
ENTRYPOINT ["java", "-jar", "application.jar"]

Kotlin DSL のプロジェクトでは、6行目を COPY gradlew settings.gradle.kts build.gradle.kts ./ に変えます。

build/libs/*.jar が1つに決まるよう、ビルドは bootJar タスクで行っています。 Spring Boot の Gradle プラグインは、bootJar を構成すると通常の jar タスクの成果物に plain という分類子を付けるため、build タスクで両方を作ると JAR が2つになります。build を使いたい場合は、公式の方法で jar タスクを止められます。

tasks.named("jar") {
    enabled = false
}

.dockerignoreの全文

ビルドコンテキスト(docker build に渡すフォルダ)から外すものを書きます。プロジェクト直下に .dockerignore として置きます。# で始まる行はコメント、* は任意の文字列、! で始まる行は「除外の例外」です。

# ビルド成果物(イメージの中で作り直す)
target/
build/
.gradle/

# バージョン管理・エディタ
.git/
.gitignore
.idea/
.vscode/
*.iml

# 秘密情報とローカル設定
.env
*.pem
*.key

# コンテナ定義とドキュメント
Dockerfile*
compose*.yaml
*.md

target/ と build/ を外しておくと、手元でビルドした成果物がイメージに混ざらず、送るファイルも小さくなります。.env や鍵ファイルを外すのは、イメージに秘密情報を入れないためです。

DB付きのcompose.yaml全文

アプリと PostgreSQL を一緒に起動する構成です。DB のヘルスチェックが通ってからアプリを起動するように depends_on の condition: service_healthy を使っています。アプリ側には PostgreSQL の JDBC ドライバーと spring-boot-starter-jdbc(または spring-boot-starter-data-jpa)が入っている前提です。

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/app
      SPRING_DATASOURCE_USERNAME: app
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD:?.env に DB_PASSWORD を設定してください}
    depends_on:
      db:
        condition: service_healthy
    mem_limit: 512m

  db:
    image: postgres:17.11
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD:?.env に DB_PASSWORD を設定してください}
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 3s
      retries: 10

volumes:
  db-data:

パスワードは同じフォルダの .env に書きます(.env は Git と .dockerignore の対象から外します)。

DB_PASSWORD=change-me-local
docker compose up --build -d

docker compose config で構文と変数の展開を確認した結果です。.env が無い状態では、指定したメッセージで止まります。

$ docker compose config
error while interpolating services.app.environment.SPRING_DATASOURCE_PASSWORD: required variable DB_PASSWORD is missing a value: .env に DB_PASSWORD を設定してください

$ echo "DB_PASSWORD=change-me-local" > .env
$ docker compose config
name: proj
services:
  app:
    build:
      context: /path/to/proj
      dockerfile: Dockerfile
    depends_on:
      db:
        condition: service_healthy
        required: true
    environment:
      SPRING_DATASOURCE_PASSWORD: change-me-local
      SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/app
      SPRING_DATASOURCE_USERNAME: app
    mem_limit: "536870912"
…(省略)…

設定のポイントは次の4つです。

項目 内容
SPRING_DATASOURCE_URL 環境変数は application.properties より優先される。spring.datasource.url をピリオドの代わりにアンダースコアで書く
ホスト名 db compose のサービス名がそのまま接続先になる
pg_isready 接続を受け付けていれば終了コード 0
ボリュームの場所 PostgreSQL 17 以前は /var/lib/postgresql/data、18 以降は /var/lib/postgresql

pg_isready の終了コードは、PostgreSQL 16 で起動中のポートと未使用のポートに対して実行すると次のようになります。

$ pg_isready -h 127.0.0.1 -p 5432; echo "exit=$?"
127.0.0.1:5432 - accepting connections
exit=0
$ pg_isready -h 127.0.0.1 -p 10401; echo "exit=$?"
127.0.0.1:10401 - no response
exit=2

5つの方式を比べる

冒頭の表を、判断に使える形で詳しくします。

方式 作り方 特徴
JAR をそのまま COPY COPY target/app.jar して java -jar 最短で書ける。JAR 全体が1つの層になる
マルチステージ ビルド段で JAR を作り、実行段へコピー ビルドに使う JDK をイメージで固定できる
レイヤー展開 extract --layers で4フォルダに分けてコピー 依存ライブラリの層を使い回せる。公式の推奨構成
AOT キャッシュ・CDS 展開後に学習実行してキャッシュを同梱 起動時間とメモリ使用量を抑えられる
Buildpacks mvn spring-boot:build-image/gradle bootBuildImage Dockerfile 不要。動作中の Docker デーモンが必要

選び方の目安は次のとおりです。

状況 選ぶ方式
まず動かして確かめたい JAR をそのまま COPY
チームで同じイメージを作り、何度も配布する マルチステージ+レイヤー展開(本記事の全文)
起動の速さが効く(台数の増減が多い、起動回数が多い) 上に AOT キャッシュ(Java 25 以降)か CDS を足す
Dockerfile の保守を減らしたい Buildpacks

レイヤー展開で分かれる4つのフォルダ

extract --layers を付けると、JAR の中身が次の4層に分かれます。上から順に変わりにくいので、この順でコピーします。

層 中身 変わる頻度
dependencies リリース版の依存ライブラリ 依存を更新したとき
spring-boot-loader JAR を起動するためのローダー Spring Boot の版を上げたとき
snapshot-dependencies SNAPSHOT 版の依存ライブラリ SNAPSHOT が更新されたとき
application アプリのクラスとリソース コードを直すたび

公式リファレンスによると、展開後の構成ではライブラリが lib/ フォルダに置かれ、application.jar はアプリのクラスと lib/ を参照するマニフェストだけを持ちます。入れ子の JAR からクラスを読む分の起動コストがなくなり、起動後の実行速度は実行可能 JAR と変わりません。使えるオプションは java -Djarmode=tools -jar my-app.jar help extract で確認できます。

4行のDockerfileを段階的に直す

よく見かける4行の Dockerfile から始めて、1段ずつ足していきます。

直す前(JAR をそのまま COPY)

FROM bellsoft/liberica-openjre-debian:25-cds
COPY target/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

この形は、手元で ./mvnw package を済ませてあれば動きます。ここに足していく内容と、得られるものを順に並べます。

段 足すもの 得られるもの
1 展開段と COPY 4回 コード変更時に変わる層が application 中心になる
2 ビルド段 手元に JDK が無くても同じ JAR が作れる
3 USER 10001:10001 root 以外で動く
4 ENV JAVA_TOOL_OPTIONS メモリ上限に合ったヒープになる
5 HEALTHCHECK 応答できる状態かを Docker が把握できる
6 学習実行と -XX:AOTCache 起動が速くなる(任意)

段1を足した後(公式リファレンスの Dockerfile と同じ形)

FROM bellsoft/liberica-openjre-debian:25-cds AS builder
WORKDIR /builder
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted

FROM bellsoft/liberica-openjre-debian:25-cds
WORKDIR /application
COPY --from=builder /builder/extracted/dependencies/ ./
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
COPY --from=builder /builder/extracted/application/ ./
ENTRYPOINT ["java", "-jar", "application.jar"]

JAR の場所は docker build --build-arg JAR_FILE=path/to/myapp.jar . で指定できます。段2〜5を足した結果が、冒頭の「コピペ用の Dockerfile 全文」です。

AOTキャッシュとCDSを足す

AOT キャッシュは、一度アプリを起動して読み込んだクラスなどの情報をファイルに保存し、次回以降の起動に使う JVM の機能です。 Spring Boot の公式リファレンスは、Java 25 以降では AOT キャッシュを、Java 24 以前では CDS を使うよう案内しています。どちらも展開した形のアプリに対して使ったときだけ効果があります。

Javaの版ごとの起動オプション

Java の版 方式 学習実行で付ける 起動時に付ける
25 以降 AOT キャッシュ -XX:AOTCacheOutput=app.aot -XX:AOTCache=app.aot
24 以前 CDS -XX:ArchiveClassesAtExit=application.jsa -XX:SharedArchiveFile=application.jsa

学習実行では、どちらも -Dspring.context.exit=onRefresh を付けます。Spring のコンテキストの初期化が終わった時点でアプリが終了するので、ビルドの途中で止まらずに進みます。キャッシュファイルは、アプリと Java の版が変わらない限り有効です。

Dockerfileに足す行(Java 25以降)

「コピペ用の Dockerfile 全文」の ENV JAVA_TOOL_OPTIONS の次に1行足し、最後の ENTRYPOINT を置き換えます。ENV より後ろに置くのは、学習実行と起動時の JVM オプションをそろえるためです。

# AOT キャッシュの学習実行(ENV JAVA_TOOL_OPTIONS の次、USER の前に置く)
RUN java -XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh -jar application.jar

# 最後の ENTRYPOINT をこれに置き換える
ENTRYPOINT ["java", "-XX:AOTCache=app.aot", "-jar", "application.jar"]

Dockerfileに足す行(Java 24以前)

# CDS の学習実行(ENV JAVA_TOOL_OPTIONS の次、USER の前に置く)
RUN java -XX:ArchiveClassesAtExit=application.jsa -Dspring.context.exit=onRefresh -jar application.jar

# 最後の ENTRYPOINT をこれに置き換える
ENTRYPOINT ["java", "-XX:SharedArchiveFile=application.jsa", "-jar", "application.jar"]

Java 24 以前で使う場合は、FROM のタグも使う Java の版に合わせます。

学習実行で気をつけること

学習実行はイメージのビルド中にアプリを起動します。公式の How-to には、Spring のライフサイクルが始まる前に外部サービスへ接続する設定(早い段階でのデータベース操作など)があると、学習実行のための設定調整が必要になる場合があると書かれています。DB に接続するアプリで AOT キャッシュを使うときは、How-to の「Preventing Remote Services Interaction During the Training Run」から案内されている設定例を確認してください。

Buildpacks で AOT キャッシュを使う場合は、Java 25 以降で、ビルド時の環境変数 BP_JVM_AOTCACHE_ENABLED を true にします。

Java 21でCDSの動きを確かめる

CDS の2つのオプションは Spring が無くても試せます。Hello クラス1つの JAR を作り、OpenJDK 21.0.10 でアーカイブを作って使った結果です。

public class Hello {
    public static void main(String[] args) {
        System.out.println("hello " + java.time.Year.now().isLeap());
    }
}
$ javac Hello.java && printf 'Main-Class: Hello\n' > m.txt && jar cfm hello.jar m.txt Hello.class

$ java -XX:ArchiveClassesAtExit=application.jsa -jar hello.jar
hello false
$ ls -l application.jsa | awk '{print $5, $9}'
200704 application.jsa

$ java -XX:SharedArchiveFile=application.jsa -Xlog:class+load:file=load.log -jar hello.jar
hello false
$ grep 'Hello ' load.log
[info][class,load] Hello source: shared objects file (top)

$ java -Xlog:class+load:file=load0.log -jar hello.jar
$ grep 'Hello ' load0.log
[info][class,load] Hello source: file:/path/to/hello.jar

アーカイブを指定すると、Hello クラスの読み込み元が JAR ファイルから shared objects file (top)(作成したアーカイブ)に変わります。一方、Java 25 向けの -XX:AOTCacheOutput を Java 21 に渡すと、次のように起動前に止まります。オプションは Java の版に合わせて選ぶ必要があることが分かります。

$ java -XX:AOTCacheOutput=app.aot -version
Unrecognized VM option 'AOTCacheOutput=app.aot'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

コンテナ向けのJVMオプション

JVM はコンテナのメモリ上限を自動で読み取り、既定ではその 25% を最大ヒープにします。 アプリ専用のコンテナではこの割合を上げると、割り当てたメモリを活用できます。

既定値の一覧(OpenJDK 21.0.10で出力)

$ java -XX:+PrintFlagsFinal -version | grep -E ' (MaxRAMPercentage|InitialRAMPercentage|MinRAMPercentage|UseContainerSupport|ExitOnOutOfMemoryError|HeapDumpOnOutOfMemoryError|ActiveProcessorCount) '
      int ActiveProcessorCount                     = -1                                        {product} {default}
     bool ExitOnOutOfMemoryError                   = false                                     {product} {default}
     bool HeapDumpOnOutOfMemoryError               = false                                  {manageable} {default}
   double InitialRAMPercentage                     = 1.562500                                  {product} {default}
   double MaxRAMPercentage                         = 25.000000                                 {product} {default}
   double MinRAMPercentage                         = 50.000000                                 {product} {default}
     bool UseContainerSupport                      = true                                      {product} {default}
オプション 既定値 使いどころ
-XX:MaxRAMPercentage=75 25 使えるメモリに対する最大ヒープの割合
-XX:InitialRAMPercentage=50 1.5625 起動時のヒープの割合
-XX:+ExitOnOutOfMemoryError 無効 メモリ不足になったらプロセスを終了する
-XX:+HeapDumpOnOutOfMemoryError 無効 メモリ不足のときヒープダンプを出す
-XX:HeapDumpPath=/tmp/app.hprof 作業フォルダ ヒープダンプの出力先
-XX:ActiveProcessorCount=2 -1(自動) JVM が使う CPU 数を明示する
-XX:-UseContainerSupport 有効 コンテナ検出を止める(通常は変更しない)
-Xmx512m 自動 最大ヒープをサイズで固定する
-Xss512k Linux/x64 は 1024 KB スレッドのスタックサイズ

メモリ上限ごとの最大ヒープ

使えるメモリを -XX:MaxRAM で指定し、割合を変えて最大ヒープ(MaxHeapSize)を出力した結果です。

$ java -XX:MaxRAM=512m -XX:MaxRAMPercentage=75 -XX:+PrintFlagsFinal -version | grep ' MaxHeapSize '
   size_t MaxHeapSize                              = 402653184                                 {product} {ergonomic}
使えるメモリ 25%(既定) 75%
512 MB 128 MB(134217728) 384 MB(402653184)
1 GB 256 MB(268435456) 768 MB(805306368)
2 GB 512 MB(536870912) 1536 MB(1610612736)
4 GB 1024 MB(1073741824) 3072 MB(3221225472)

ヒープ以外にも、メタスペース・スレッドのスタック・コードキャッシュなどがメモリを使います。ヒープは上限の 75% 前後から始め、残りをヒープ以外の領域に残すと、上限に達しにくくなります。

JAVA_TOOL_OPTIONSで渡す

ENV JAVA_TOOL_OPTIONS に書いたオプションは、ENTRYPOINT を書き換えなくても JVM が拾います。拾ったときは標準エラーに1行出ます。

$ JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75 -XX:+ExitOnOutOfMemoryError" java -XX:+PrintFlagsFinal -version 2>&1 | grep -E 'Picked up| (MaxRAMPercentage|ExitOnOutOfMemoryError) '
Picked up JAVA_TOOL_OPTIONS: -XX:MaxRAMPercentage=75 -XX:+ExitOnOutOfMemoryError
     bool ExitOnOutOfMemoryError                   = true                                      {product} {environment}
   double MaxRAMPercentage                         = 75.000000                                 {product} {environment}

docker run -e JAVA_TOOL_OPTIONS="…" や compose の environment で上書きできるので、環境ごとに割合を変えたいときにも使えます。

ExitOnOutOfMemoryErrorの効果

別スレッド(worker-1)で 1 MB の配列を追加し続け、メインスレッドはその終了を待つだけのプログラムを、ヒープ 32 MB で実行した結果です。

import java.util.ArrayList;
import java.util.List;

public class OomThread {
    public static void main(String[] args) throws Exception {
        Thread worker = new Thread(() -> {
            List<byte[]> list = new ArrayList<>();
            while (true) {
                list.add(new byte[1024 * 1024]);
            }
        }, "worker-1");
        worker.start();
        worker.join();
        System.out.println("main: worker が止まった後も動いています");
    }
}
$ java -Xmx32m OomThread; echo "exit=$?"
Exception in thread "worker-1" java.lang.OutOfMemoryError: Java heap space
    at OomThread.lambda$main$0(OomThread.java:9)
…(省略)…
main: worker が止まった後も動いています
exit=0

$ java -Xmx32m -XX:+ExitOnOutOfMemoryError OomThread; echo "exit=$?"
Terminating due to java.lang.OutOfMemoryError: Java heap space
exit=3

オプションなしでは、メモリ不足になったスレッドだけが止まり、プロセスは動き続けて終了コード 0 で終わっています。Web アプリでも、リクエストを処理するスレッドでメモリ不足が起きるとプロセスは残ります。-XX:+ExitOnOutOfMemoryError を付けておくと、どのスレッドで起きてもプロセスが終了コード 3 で終了し、コンテナの再起動の仕組みに任せられます。

3系と4系で変わる点

Dockerfile に関係する変更を、公式の移行ガイドとリリースノートから抜き出しました。

項目 3.5 4.x
jarmode=layertools 非推奨 4.1 で削除。jarmode=tools を使う
公式例のベースイメージ liberica-openjre-debian:24-cds liberica-openjre-debian:25-cds
liveness/readiness 設定で有効にする 4.0 から既定で有効
起動スクリプト埋め込みの JAR 使える 4.0 で削除。java -jar で起動
Undertow 使える 4.0 で削除。Tomcat か Jetty
Maven の -DskipTests AOT 処理も省く 4.1 から AOT 処理は省かない

3.5.16 のリファレンスの Dockerfile も jarmode=tools と extract --layers --destination extracted を使っているので、3.5 のうちに tools へ書き換えておけば、4 系に上げても Dockerfile はそのまま使えます。 展開の書き方は次のように対応します。

# layertools を使っていた書き方
RUN java -Djarmode=layertools -jar application.jar extract

# tools を使う書き方(3.5・4.x 共通)
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted

ビルド段で -Dmaven.test.skip=true を使っているのは、4.1 で Spring Boot の Maven プラグインがこのプロパティだけを見るようになったためです。

停止時の動き

Spring Boot 4.1.1 では、組み込みの Web サーバー(Tomcat・Jetty・Reactor Netty)のグレースフルシャットダウンが既定で有効です。停止の合図を受けると、処理中のリクエストを完了させ、新しいリクエストは受け付けません。待つ時間は次の設定で変えられます。

spring:
  lifecycle:
    timeout-per-shutdown-phase: "20s"

この動きを生かすには、ENTRYPOINT を exec 形式(["java", "-jar", …])で書き、停止の合図が Java のプロセスに直接届くようにします。docker stop の待ち時間(-t)は、上の設定より長くしておきます。

よくあるつまずきと直し方

症状 原因 直し方
COPY で JAR が複数見つかる Gradle の -plain.jar も一致している bootJar でビルドするか jar タスクを止める
./mvnw が実行できない 実行権限が無い、または .mvn/ をコピーしていない chmod +x mvnw してコミットし、.mvn/ もコピーする
Unrecognized VM option 'AOTCacheOutput…' Java 24 以前で AOT キャッシュのオプションを使った CDS のオプションに替えるか Java 25 以降にする
キャッシュを付けても起動が変わらない 展開していない JAR に対して使っている extract 後の application.jar で学習・起動する
ヘルスチェックが unhealthy のまま Actuator が無い、またはポートが違う spring-boot-starter-actuator を入れ、URL のポートを合わせる
起動直後に unhealthy になる 起動に --start-period より時間がかかる --start-period を起動時間より長くする
DB より先にアプリが起動して接続に失敗する depends_on が起動順だけを見ている condition: service_healthy と DB の healthcheck を使う
コードを1行直しただけで全層が変わる COPY src/ より前で依存を解決していない pom.xml だけ先にコピーして依存を解決する

よくある質問

Q. JRE と JDK のどちらのイメージを使えばよいですか? A. ビルド段はコンパイルに JDK が必要なので JDK のイメージ、実行段は JRE のイメージを使います。公式リファレンスの例も実行用に bellsoft/liberica-openjre-debian:25-cds を使っています。本記事の全文はこの組み合わせです。

Q. Java 21 で使う場合はどこを変えますか? A. FROM のタグを 21 のものに替え、AOT キャッシュの代わりに CDS(-XX:ArchiveClassesAtExit と -XX:SharedArchiveFile)を使います。レイヤー展開・非 root・JVM オプション・HEALTHCHECK はそのまま使えます。

Q. HEALTHCHECK に curl を使ってもよいですか? A. イメージに curl が入っていれば、Docker 公式の例と同じ CMD curl -f http://localhost:8080/actuator/health/readiness || exit 1 の形で書けます。本記事では、イメージに追加のパッケージを入れずに済むよう Java のクラスを使いました。

Q. Dockerfile と Buildpacks はどちらがよいですか? A. どちらも公式がサポートする方法です。ベースイメージや層の構成を自分で決めたい場合は Dockerfile、Dockerfile を保守せずにコマンド1つで作りたい場合は Buildpacks(mvn spring-boot:build-image/gradle bootBuildImage)が合います。

まとめ

  • 基本形は3段:ビルド段(JDK)→ 展開段(jarmode=tools の extract --layers)→ 実行段(JRE)。
  • COPY は変わりにくい順:dependencies → spring-boot-loader → snapshot-dependencies → application。
  • 起動を速くするなら、Java 25 以降は AOT キャッシュ(-XX:AOTCacheOutput/-XX:AOTCache)、24 以前は CDS(-XX:ArchiveClassesAtExit/-XX:SharedArchiveFile)。
  • JVM の最大ヒープは既定で上限の 25%。-XX:MaxRAMPercentage=75 を JAVA_TOOL_OPTIONS で渡すと、512 MB の上限で 384 MB になる。
  • HEALTHCHECK は readiness を確認。4.0 からプローブは既定で有効。
  • compose では condition: service_healthy で DB の準備を待ってからアプリを起動する。
  • 4.1 で layertools は削除。jarmode=tools に統一する。

参考・出典

確認日はいずれも 2026年10月7日です。

  • Spring Boot リファレンス Dockerfiles(4.1.1): https://docs.spring.io/spring-boot/reference/packaging/container-images/dockerfiles.html
  • Spring Boot リファレンス Dockerfiles(3.5.16): https://docs.spring.io/spring-boot/3.5/reference/packaging/container-images/dockerfiles.html
  • AOT Cache(4.1.1): https://docs.spring.io/spring-boot/reference/packaging/aot-cache.html
  • How-to AOT Cache(4.1.1): https://docs.spring.io/spring-boot/how-to/aot-cache.html
  • Efficient Deployments(4.1.1): https://docs.spring.io/spring-boot/reference/packaging/efficient.html
  • Cloud Native Buildpacks(4.1.1): https://docs.spring.io/spring-boot/reference/packaging/container-images/cloud-native-buildpacks.html
  • Gradle プラグイン Packaging Executable Archives(4.1.1): https://docs.spring.io/spring-boot/gradle-plugin/packaging.html
  • Actuator Endpoints(4.1.1): https://docs.spring.io/spring-boot/reference/actuator/endpoints.html
  • Graceful Shutdown(4.1.1): https://docs.spring.io/spring-boot/reference/web/graceful-shutdown.html
  • Externalized Configuration(4.1.1): https://docs.spring.io/spring-boot/reference/features/external-config.html
  • Spring Boot 4.0 Migration Guide: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide
  • Spring Boot 4.1 Release Notes: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.1-Release-Notes
  • Dockerfile reference(HEALTHCHECK・USER): https://docs.docker.com/reference/dockerfile/
  • Compose file reference(services): https://docs.docker.com/reference/compose-file/services/
  • Build context(.dockerignore): https://docs.docker.com/build/concepts/context/
  • Docker Hub bellsoft/liberica-openjdk-debian: https://hub.docker.com/r/bellsoft/liberica-openjdk-debian/tags
  • Docker Hub postgres: https://hub.docker.com/_/postgres
  • The java Command(JDK 21): https://docs.oracle.com/en/java/javase/21/docs/specs/man/java.html
  • Maven Dependency Plugin dependency:go-offline: https://maven.apache.org/plugins/maven-dependency-plugin/go-offline-mojo.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Spring Boot Dockerfile|コピペ用全文とAOTキャッシュ設定」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?