Spring BootアプリケーションのDockerデプロイでつまずく原因の多くは、手順そのものではなく、古い記事のコマンドをそのまま使ってしまうことにあります。ベースイメージに広く使われてきたopenjdkは公式に非推奨となり、openjdk:11-jre-slimのようなタグは既に取得できません。レイヤ抽出に使われていた-Djarmode=layertoolsもSpring Boot 4.1で削除されました。ここでは4.1時点の公式仕様に沿って、実行可能jarの作成からコンテナ起動、レジストリ配布までを4ステップで整理します。
まとめ
Spring BootアプリのDockerデプロイは、実行可能jarの作成 → イメージ化 → コンテナ起動 → レジストリ配布の4ステップで完結します。判断が必要なのはイメージ化の方式だけで、Dockerfileを書かずに済ませたいならmvn spring-boot:build-image(GradleならbootBuildImage)、ベースイメージや構成を自分で決めたいならマルチステージのDockerfileを選びます。
ベースイメージにはeclipse-temurinやbellsoft/liberica-openjre-debianを使います。設定はイメージに焼き込まず環境変数で外に出します。Spring Bootの外部設定はOS環境変数がapplication.propertiesより優先されるため、同じイメージのまま環境ごとの値を切り替えられます。以下、各ステップで実際に打つコマンドと判断基準を見ていきます。
デプロイまでの4ステップと、どこで判断が要るか
Spring Bootは組み込みサーバを持つため、外部のTomcatにwarを配置する従来型のデプロイは不要です。実行可能jarが1つあればjava -jarで起動できるので、Dockerデプロイは「そのjarをイメージに閉じ込め、どこでも同じように起動する」作業になります。
| ステップ | 成果物 | 代表コマンド | 判断の要否 |
|---|---|---|---|
| 1. jar作成 | 実行可能jar | mvn clean package |
不要 |
| 2. イメージ化 | コンテナイメージ | spring-boot:build-image・docker build |
必要 |
| 3. 起動 | 稼働中コンテナ | docker run |
不要 |
| 4. 配布 | レジストリ上のイメージ | docker push |
配布先の選定のみ |
ステップ1・3・4はコマンドがほぼ固定で、環境が変わっても書き換えるのはイメージ名とポート番号だけです。Dockerのイメージとコンテナの関係に不安があるなら、先にDockerの仕組みを原理から理解するでレイヤとコンテナ隔離の対応を押さえておくと、以降のマルチステージビルドの説明が読みやすくなります。
ステップ1:実行可能jarの作成
Spring Initializrでの雛形作成と依存関係の選択
新規に作るならSpring Initializrで雛形を生成します。2026年8月時点の既定値はSpring Boot 4.1.0、Java 17、ビルドツールはGradleです。Javaは17・21・25・26から選べます。Spring Boot 4.1.0の要件はJava 17以上26まで、Maven 3.6.3以上、Gradle 8.14以上または9.xなので、既定のまま生成すれば要件は満たされます。
Webアプリとしてデプロイするなら依存関係にspring-boot-starter-webを追加します。この1つで組み込みTomcat(4.1系はTomcat 11.0.x、Jettyを選ぶなら12.1.x。いずれもServlet 6.1)まで入るため、別途サーブレットコンテナを用意する必要はありません。既存プロジェクトを4系へ上げる場合の変更点はSpring Boot 4とは?最新バージョン4.1の変更点に整理しています。
MavenとGradleでのjar生成コマンド
実行可能jarはビルドツールのパッケージングタスクで作ります。Mavenはtarget/、Gradleはbuild/libs/に出力され、この違いは後のDockerfileにそのまま響きます。
mvn -B clean package
./gradlew clean bootJar
ここで生成されるのは、依存ライブラリを内包した単一のjar(uber jar)です。次のステップでこのjarをそのままコピーするか、レイヤに展開してからコピーするかで、ビルドキャッシュの効き方が変わります。
ステップ2:イメージ化の2方式と選び方
Spring Bootの公式ドキュメントは、コンテナ化の方法としてDockerfileとCloud Native Buildpacksの2つを併記しています。どちらが正解ということはありません。
Buildpacks:Dockerfileを書かない方式
Spring BootのビルドプラグインにはBuildpacks対応が組み込まれており、コマンド1つでイメージが出来上がります。Dockerfileは不要です。
mvn spring-boot:build-image
./gradlew bootBuildImage
既定のビルダーはpaketobuildpacks/builder-noble-java-tiny:latest、生成されるイメージ名はMavenがdocker.io/library/${project.artifactId}:${project.version}、Gradleがdocker.io/library/${project.name}:${project.version}です。既定のPaketoビルダーを使う場合、プラグインはプロジェクトのJavaターゲットバージョン(Mavenならmaven.compiler.target)を検出して同じバージョンをイメージへ導入します。生成イメージは非rootユーザーで実行されるため、後述のroot実行の問題を意識せずに済みます。
既定ビルダーには制約が1つあります。公式はbuilder-noble-java-tinyについて「contains a reduced set of system libraries and does not include a shell」と注記しており、起動スクリプトなどシェルを必要とするアプリはrunImageをpaketobuildpacks/ubuntu-noble-run:latestのようにシェルを含むイメージへ差し替える必要があります。Dockerデーモンへのアクセスも必須ですが、ローカルのDocker Desktopに限りません。DOCKER_HOSTやDOCKER_CONTEXTで接続先を指定でき、公式ドキュメントにはpodmanやminikubeのデーモンを使う設定例も用意されています。
マルチステージDockerfile:構成を自分で決める方式
ベースイメージを固定したい、社内の承認済みイメージしか使えない、といった制約があるならDockerfileを書きます。Spring Boot公式が示す現行の書き方は、jarをレイヤに展開してからコピーするマルチステージ構成です。
FROM bellsoft/liberica-openjre-debian:25-cds AS builder
WORKDIR /builder
# Mavenの場合。Gradleなら build/libs/*.jar に変更する
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"]
ARG JAR_FILEの既定値はMavenの出力先です。Gradleでビルドしたならbuild/libs/*.jarへ書き換えないと、docker buildがjarを見つけられずに失敗します。公式例にも同じ注記が入っています。
4回に分けてCOPYしているのは、変更頻度の違う内容を別レイヤに置くためです。依存ライブラリはリリースをまたいでも変わらず、アプリのクラスだけが毎回変わります。分けておけば、再ビルド時にpushされるのも取得されるのも最後のレイヤだけで済みます。公式はこの展開レイアウトを「The default layout is the most efficient, and it is AOT cache (and CDS) friendly.」と説明しており、ベースイメージに-cds付きタグを選んでいるのはこの相性のためです。命令ごとの挙動はDockerfileとは|書き方・主要命令・ベストプラクティス、Spring Boot固有の構成はSpring BootのDockerfileの書き方で個別に扱っています。
どちらを選ぶかの判断基準
| 観点 | Buildpacks | マルチステージDockerfile |
|---|---|---|
| 保守するファイル | なし | Dockerfile |
| ベースイメージ | ビルダー任せ | 自分で固定 |
| 非root実行 | 既定で有効 | 自分で記述 |
| シェルの有無 | 既定ビルダーは無し | ベースイメージ次第 |
| 向く場面 | 標準構成で早く回す | イメージ要件が決まっている |
迷うならBuildpacksから始めるのが妥当です。Dockerfileは書いた時点から陳腐化が始まり、ベースイメージのタグ更新やJavaの追随を人が負い続けることになります。この記事の2024年版がopenjdk:11-jre-slimのまま2026年8月まで残っていたのが、まさにその例です。逆に、ベースイメージが監査対象になっている組織ではビルダー任せにできないため、Dockerfileを書いて内容を説明できる状態にしておくべきです。
ベースイメージの選定|openjdkが取得できなくなった後の移行先
2024年以前に書かれたSpring Boot × Dockerの記事の多くは、openjdk:11-jre-slimやopenjdk:17-slimを使っています。このopenjdkはDocker公式イメージとして非推奨で、公式ページには「This image is officially deprecated and all users are recommended to find and use suitable replacements ASAP.」と明記されています。
問題は非推奨にとどまりません。2026年8月17日時点でレジストリに問い合わせたところ、openjdk:11-jre-slim、openjdk:17-slim、openjdk:11、openjdk:17、openjdk:latestはいずれもマニフェストが404を返しました。応答があったのはopenjdk:27-ea-jdkのようなEarly Accessタグだけです。古いDockerfileは「脆弱なまま動く」のではなく、docker buildの最初のFROMで止まります。公式が代替の例として挙げるのはamazoncorretto、eclipse-temurin、ibm-semeru-runtimes、ibmjava、sapmachineで、これは網羅列挙ではなく例示です。
| 用途 | タグ例 | 備考 |
|---|---|---|
| 標準的な移行先 | eclipse-temurin:21-jre |
公式イメージ・JREのみ |
| サイズ優先 | eclipse-temurin:21-jre-alpine |
musl libc |
| 公式例に合わせる | bellsoft/liberica-openjre-debian:25-cds |
ベンダー提供・CDS対応 |
| 取得不能 | openjdk:11-jre-slim |
タグ削除済み |
なお、Spring Boot公式のDockerfile例が使うbellsoft/liberica-openjre-debianは上の5種に含まれません。Docker公式イメージではなくBellSoft提供のイメージなので、社内規約でDocker公式イメージに限定しているならeclipse-temurinを選びます。
タグはlatestではなくメジャーバージョンまで固定します。Initializrの既定はJava 17、上の表の移行先は21、公式Dockerfile例は25と数字がばらつきますが、条件は「実行側のJava ≧ ビルド側のJava」の一点だけです。ビルドを17で行い実行を25にするのは問題なく、逆にすると起動時にクラスファイルのバージョン不一致で落ちます。使用中のSpring Bootがどのバージョンまで上げられるかはSpring Bootバージョン一覧とサポート期限で確認できます。
ステップ3:コンテナの起動と設定の外出し
ビルドと起動のコマンド
Dockerfile方式の場合、イメージのビルドと起動は次の2コマンドです。-pでホスト側8080をコンテナ側8080へ割り当てます。
docker build -t myapp:1.0.0 .
docker run -d --name myapp -p 8080:8080 myapp:1.0.0
起動後はdocker logs myappで確認します。Spring Bootが出すのはStarted DemoApplication in 2.345 secondsのような行で、アプリケーション名の位置にはメインクラスの単純名が入ります。この行が出ていれば起動は成功しており、ブラウザから応答が無いならポート割り当て側を疑います。起動直後に終了してしまうケースの切り分けはDockerコンテナ起動コマンドの使い分けで扱っています。
application.propertiesを環境変数で上書きする
接続先URLやパスワードをapplication.propertiesに書いたままイメージに焼き込むと、環境ごとに別のイメージを作る羽目になります。Spring Bootの外部設定では、OS環境変数がapplication.propertiesより優先されます。同じイメージのまま値だけ差し替えられるので、検証環境と本番環境で同一のイメージを使えます。
docker run -d -p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=prod \
--env-file ./prod.env \
myapp:1.0.0
プロパティ名から環境変数名への変換は3段階です。公式は「Replace dots (.) with underscores (_).」「Remove any dashes (-).」「Convert to uppercase.」と定めています。ハイフンは置き換えではなく削除である点に注意してください。spring.datasource.urlはSPRING_DATASOURCE_URLで直感どおりですが、spring.main.log-startup-infoはSPRING_MAIN_LOGSTARTUPINFOです。ハイフンをアンダースコアにした名前はどのプロパティにも一致せず、エラーも警告も出ないまま既定値で起動します。ケバブケースのプロパティを外出しするとき最も踏みやすい失敗です。
パスワードのような秘密情報は-eで直接渡さないでください。docker inspectやプロセス一覧から平文で読めてしまいます。上の例のように--env-fileでファイルから読み込むか、実行基盤のシークレット機構を使います。
優先順位には上限もあります。環境変数はapplication.propertiesより強い一方、Javaシステムプロパティやコマンドライン引数には負けます。イメージ名の後ろに--spring.profiles.active=stgのような引数を付けると、そちらが環境変数の指定を上書きします。切り替え手段は一方に統一するのが無難です。
ステップ4:配布先での起動とCI/CDでの自動化
レジストリ経由でイメージを運ぶ
手元でビルドしたイメージを本番へ運ぶには、レジストリを経由します。レジストリのホスト名を含むタグを付け直し、認証してからpushします。
echo $GITHUB_PAT | docker login ghcr.io -u USERNAME --password-stdin
docker tag myapp:1.0.0 ghcr.io/example/myapp:1.0.0
docker push ghcr.io/example/myapp:1.0.0
配布先のホストでは、同じイメージ名を指定して取得し起動します。ビルドは配布先では行いません。
docker pull ghcr.io/example/myapp:1.0.0
docker run -d --name myapp -p 8080:8080 --env-file ./prod.env \
--restart unless-stopped ghcr.io/example/myapp:1.0.0
--restart unless-stoppedを付けておくと、ホストの再起動後にコンテナが自動で立ち上がります。複数コンテナをまとめるならCompose、スケールや無停止更新まで要るならECSやKubernetesへ移りますが、イメージを作って配るという前段は変わりません。
タグ設計とCI/CDへの載せ方
タグにはコミットハッシュやリリースバージョンを使い、latestだけで運用しないでください。latestは可変で、実行基盤が取得した時点によって中身が変わります。障害時に「本番で動いていたのはどのビルドか」を特定できず、ロールバック先も指定できません。両方付けるのは構いませんが、実行基盤が参照するのはバージョン付きタグにします。
GitHub Actionsに載せる場合、認証からpushまでは公式アクション2つで完結します。テストを通ったコミットだけがイメージになるよう、テストジョブの後段に置きます。
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/example/myapp:${{ github.sha }}
タグにgithub.shaを使えば、どのコミットから作られたイメージかが名前だけで分かります。リリース時に改めてバージョン付きタグを付け直す運用にすると、日常のビルドと配布物の管理を分けられます。
デプロイでつまずく4つの落とし穴
layertools形式の廃止と版ごとの挙動差
2020年から2023年頃の記事は、レイヤ抽出にjava -Djarmode=layertools -jar app.jar extractを使っています。この形式はSpring Boot 4.1で使えなくなりました。ソースツリーを確認するとLayerToolsJarModeは3.3.0・3.5.16・4.0.7には存在し、4.1.0では削除されています。Maven Centralでもspring-boot-jarmode-layertoolsは3.2.12が最終公開で、3.3.0からspring-boot-jarmode-toolsに置き換わりました。
つまり3.5系や4.0系のうちは古い書き方でも動き、4.1へ上げた瞬間にビルドが失敗します。現行の書き方はjava -Djarmode=tools -jar application.jar extract --layers --destination extractedで、サブコマンドの位置とオプションの両方が違います。バージョンアップ前にDockerfileをlayertoolsで検索しておくと、移行時の事故を1つ減らせます。
VOLUMEでホスト側のパスは指定できない
古いDockerfile例に頻出するVOLUME /tmpを「コンテナの/tmpがホストと共有される」と説明している記事がありますが、これは誤りです。Docker公式リファレンスは「The VOLUME instruction creates a mount point with the specified name and marks it as holding externally mounted volumes from native host or other containers.」と述べたうえで、「you can’t mount a host directory from within the Dockerfile. The VOLUME instruction does not support specifying a host-dir parameter.」と明記しています。
VOLUMEは「ここは外部からマウントされうる場所だ」と宣言するだけで、ホストのどのディレクトリに対応するかはDockerfileでは決められません。何も指定せずに起動すればDocker管理下のボリュームが割り当てられます。ホストの特定ディレクトリを見せたいなら起動時に-vで指定します。
Dockerfile方式でのroot実行の既定
Dockerfileを自分で書く場合、明示しなければコンテナ内のプロセスはrootで動きます。Buildpacksの生成イメージは非rootで実行されるため、自前Dockerfileへ切り替えたときだけ権限が緩くなるという逆転が起きがちです。実行用ユーザーを作って切り替えます。
RUN useradd --create-home --shell /usr/sbin/nologin app
USER app
ENTRYPOINT ["java", "-jar", "application.jar"]
コンテナ内localhostの指し先
コンテナの中から見たlocalhostは、そのコンテナ自身です。データベース接続先をlocalhost:5432のままイメージ化すると、ホストにもDBコンテナにも到達しません。接続先はサービス名やホスト名で指定し、値は前述の環境変数で外から渡します。
よくある質問
Dockerでのデプロイとは何を指しますか?
アプリケーションと実行に必要な依存関係を1つのイメージにまとめ、そのイメージからコンテナを起動して稼働状態にすることを指します。Spring Bootの場合は、実行可能jarをイメージに含め、レジストリ経由で実行環境へ配布し、そこでdocker pullとdocker runを実行するまでが一連の流れです。
Spring BootのDockerイメージはDockerfileとBuildpacksのどちらで作るべきですか?
ベースイメージや構成に要件がなければBuildpacksを選びます。ベースイメージが監査対象、シェルを必要とする起動スクリプトがある、独自パッケージを追加したいといった要件があればDockerfileを書きます。
application.propertiesの値をコンテナごとに変えるにはどうすればよいですか?
環境変数で渡します。変換規則はピリオドをアンダースコアに置き換え、ハイフンを削除し、大文字にする、の3段階です。spring.main.log-startup-infoならSPRING_MAIN_LOGSTARTUPINFOで、ハイフンをアンダースコアに置き換えると一致せず、無言で既定値のまま動きます。
openjdk:11-jre-slim を使い続けても問題ありませんか?
そもそも取得できません。2026年8月17日時点でこのタグのマニフェストは404を返し、docker buildはFROMの行で失敗します。eclipse-temurin:21-jreなど現在も配布されているイメージへ移行し、アプリのビルドに使ったJavaバージョン以上のタグを選んでください。
Dockerイメージのビルドが毎回遅いのですが改善できますか?
jarをそのままCOPYしていると、アプリのクラスを1行変えただけでも依存ライブラリを含む単一レイヤが作り直されます。前掲のマルチステージDockerfileのようにレイヤへ展開して積むとキャッシュが効きます。Buildpacksではこの分割が既定で行われます。