Spring

Spring BootのDockerデプロイ手順|イメージ作成3方式・Compose・本番配布を4.1.1で検証

Spring BootのDockerデプロイ手順|イメージ作成3方式・Compose・本番配布を4.1.1で検証

Spring BootアプリをDockerで本番に出す作業は、jarを作り、イメージにし、レジストリ経由で配布先へ運んで起動する、の一本道です。迷いやすいのは、イメージを何で作るか、DBとどう組むか、更新のたびの停止で処理中のリクエストを落とさないか、の3点です。この記事では、Spring Boot 4.1.1(2026年8月20日公開・10月時点の最新GA)とJava 25の雛形を使い、2026年10月12日に手元のDocker 29.5.2で実際に動かした結果をもとに手順を整理します。

まとめ

  • イメージの作り方:要件が無ければ mvn spring-boot:build-image(Buildpacks)を使います。ベースイメージを固定したいならDockerfile、Dockerデーモンを置けないCIならJibです。
  • DBと組むとき:depends_on を書くだけでは、DBのコンテナが起動したところまでしか待ちません。DBに healthcheck を付け、condition: service_healthy を指定します。
  • 停止の猶予:Spring Bootは処理中のリクエストを最大30秒待ってから止まりますが、Dockerの既定の猶予は10秒です。20秒かかるリクエストは途中で切れました。stop_grace_period を延ばします。
  • 配布:コミットハッシュでタグを付けてGHCRなどへpushし、配布先では docker pull して起動します。GitHub Actionsで先にjarをビルドする場合は、build-push-action に context: . の指定が必要です。

以下、工程の順に、実際に打つコマンドと判断の分かれ目を見ていきます。

jar作成から本番起動までの4工程

この記事で使うSpring Web MVCの構成では、既定で組み込みTomcatを使うため、外部のアプリケーションサーバーへwarを配置しなくても、実行可能jarが1つあれば起動できます。Dockerでのデプロイは、そのjarをイメージに閉じ込めて、どこでも同じように起動するための作業です。

工程 成果物 代表コマンド 判断
1. jar作成 実行可能jar mvn package Javaの版
2. イメージ化 OCIイメージ spring-boot:build-image 3方式から選ぶ
3. 起動 稼働中コンテナ docker compose up DB待ち・停止猶予
4. 配布 レジストリ上のイメージ docker push タグ設計

以下では工程2と3を中心に説明します。工程1ではJavaとビルド設定を、工程4ではレジストリの認証とタグ設計を確認します。

実行可能jarの作成と出力先

Spring Initializr の既定は、2026年10月12日時点でSpring Boot 4.1.1、Java 17、Gradleです。Javaは17・21・25・27から選べます。ただし、4.1.1の動作要件は「at least Java 17 and is compatible with versions up to and including Java 26」で、27は互換の範囲外です。本番に出すなら、LTSの21か25を選びます。

この記事のDB接続とヘルスチェックまで再現する場合は、依存関係に「Spring Web」「Spring Boot Actuator」「Spring Data JDBC」「PostgreSQL Driver」を追加します。「Spring Web」を選ぶと、生成される pom.xml には spring-boot-starter-webmvc が入ります。4.0で spring-boot-starter-web から改称されたためで、古い記事の依存名をそのまま書き写しても新しい名前にはなりません(改称の経緯は Spring Bootとは|4.1系の依存名の変更と最小Web APIの作り方)。jarは次のコマンドで作ります。

./mvnw -B clean package        # target/demo-0.0.1-SNAPSHOT.jar
./gradlew clean bootJar         # build/libs/demo-0.0.1-SNAPSHOT.jar

Mavenは target/、Gradleは build/libs/ に出力され、この差は後でDockerfileのCOPY元を書くときに効いてきます。Spring Data JDBCを入れた雛形には @SpringBootTest のテストが付いており、DBに接続できないとテストが失敗します。手元で試すだけならテスト用DBを起動して接続先を渡すか、-DskipTests を付けます(この記事の検証は後者で行いました)。

イメージを作る3つの方式と選び方

Spring Bootのjarをイメージにする方法は、Buildpacks、Dockerfile、Jibの3つです。同じ雛形をそれぞれでイメージ化し、中身を比べました。

Buildpacks:mvn spring-boot:build-image

Spring Bootのビルドプラグインに組み込まれた方式で、Dockerfileを書かずにイメージができます。

./mvnw spring-boot:build-image   # Gradleは ./gradlew bootBuildImage

手元では72秒で docker.io/library/demo:0.0.1-SNAPSHOT ができました。名前は artifactId:version から自動で決まります。ログには「Using Java version 25 extracted from MANIFEST.MF」と出て、jarのビルドに使ったJavaの版に合わせてBellSoft Liberica JRE 25.0.4が入りました。実行ユーザーは 1002:1001 で、プラグインのドキュメントにある「images build and run as non-root users」どおりです。

制約もあります。既定のビルダー paketobuildpacks/builder-noble-java-tiny で作ったイメージにはシェルが入っておらず、docker run --entrypoint /bin/sh は「stat /bin/sh: no such file or directory」で失敗しました。起動スクリプトをシェルで書いているアプリは、runImage をシェル入りのイメージに差し替える必要があります。もう1つ、ドキュメントに「The build-image goal requires access to a Docker daemon.」とあるとおり、Dockerデーモンが要ります。Buildpacksの仕組みやベースイメージ差し替え(rebase)の運用は Cloud Native Buildpacksとは?Dockerfile不要のイメージ生成とリベース運用を実装目線で解説 にまとめています。

Dockerfileによるベースイメージの指定とレイヤー展開

社内で承認されたベースイメージしか使えない、OSパッケージを足したい、といった要件があるならDockerfileを書きます。検証に使ったのは、jarをレイヤーに展開してからコピーする次の構成です。

FROM eclipse-temurin:25-jre AS builder
WORKDIR /builder
COPY target/*.jar application.jar
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted

FROM eclipse-temurin:25-jre
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/ ./
USER 1000
ENTRYPOINT ["java", "-jar", "application.jar"]

古い記事の書き方には、今では動かない箇所が2つあります。1つは openjdk:11-jre-slim などのベースイメージです。openjdk は公式に非推奨となり、2026年10月12日にレジストリへ問い合わせると 11-jre-slim・17-slim・21・latest はいずれもHTTP 404でした(応答したのは 27-ea などのEarly Accessタグだけ)。手元にキャッシュが無ければ FROM の行で止まります。もう1つは -Djarmode=layertools で、Spring Boot 4.1.0で削除されました。いまは上の -Djarmode=tools ... extract --layers を使います。Gradleでの書き方、ビルドキャッシュ、AOTキャッシュ、ヒープ設定までの各行の理由は Spring BootのDockerfileの書き方|マルチステージ・レイヤー展開・AOTキャッシュを4.1.1で検証 で扱っています。

Jib:Dockerデーモンを使わずにレジストリへ直接push

Jib はGoogleのOSSで、MavenやGradleのプラグインがイメージを組み立てて、そのままレジストリへpushします。2026年10月時点の最新は jib-maven-plugin 3.5.2、jib-gradle-plugin 3.5.4です。pom.xmlに追記しなくても、次のコマンドで試せます。JibはDockerの認証情報(~/.docker/config.json)も読むため、先に後述の docker login ghcr.io を済ませておけばpushできます。example は自分のユーザー名か組織名に置き換えてください。

./mvnw com.google.cloud.tools:jib-maven-plugin:3.5.2:build \
  -Dimage=ghcr.io/example/myapp:1.0.0

手元にregistry:2を立ててpush先にしたところ、9秒で完了しました。ベースイメージは指定していませんが、Java 25のプロジェクトだったため eclipse-temurin:25-jre が自動で選ばれています。Docker CLI経由でローカルに作る jib:dockerBuild でも26秒でした。

注意点が2つあります。まず、既定では実行ユーザーが設定されず、rootで動きます。非rootにするには jib.container.user の指定が要ります。もう1つ、再現性のためにイメージの作成日時が1970年1月1日に固定されます。docker images の「CREATED」で新旧を見分ける運用とは相性がよくありません。

3方式の選び方

観点 Buildpacks Dockerfile Jib
Dockerデーモン 必要 構成による 不要
書くファイル なし Dockerfile なし(設定はpom)
ベースイメージ Liberica JRE(自動) 自分で固定 eclipse-temurin(自動)
既定の実行ユーザー 非root(1002) 記述しだい root
シェル なし(既定ビルダー) ベースしだい あり(temurin)

Dockerfileのビルドには、Docker Engineに組み込まれたBuildKitのほか、Docker Engineを置かないスタンドアロンのBuildKitも使えます。この記事ではDocker Engineを使う手順を示しています。

迷ったらBuildpacksから始めてください。どの方式でも、脆弱性修正を反映するための再ビルドと再配布は必要です。そのうえでDockerfile方式は、ベースイメージのタグを書き換える作業まで人が負います。この記事の2024年版が openjdk:11-jre-slim のまま残っていたのは、その作業が止まった例です。ベースイメージが監査対象の組織ならDockerfileを書き、CIのランナーでDockerデーモンを動かせない(Kubernetes上のランナーなど)ならJibを選びます。

Docker ComposeによるPostgreSQLとの同時起動

Spring BootアプリをDBと組み合わせて動かすには、接続設定に加えてDBの準備完了を待つ設定が必要です。そこで、アプリとPostgreSQL 18を1つの compose.yaml で起動する構成を検証しました。実行前に同じディレクトリへ prod.env と db.env を作成し、前者に SPRING_DATASOURCE_PASSWORD、後者に POSTGRES_PASSWORD を同じ値で設定します。両ファイルはGit管理から除外してください。次が、実際にうまく動いた定義です。

services:
  app:
    image: ghcr.io/example/myapp:1.0.0
    ports:
      - "8080:8080"
    env_file: ./prod.env          # SPRING_DATASOURCE_PASSWORD などの秘密情報
    environment:
      SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/app
      SPRING_DATASOURCE_USERNAME: app
    depends_on:
      db:
        condition: service_healthy
    stop_grace_period: 40s
    restart: unless-stopped
  db:
    image: postgres:18
    env_file: ./db.env            # POSTGRES_PASSWORD など
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
    volumes:
      - pgdata:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 3s
      retries: 10
volumes:
  pgdata:

docker compose up -d から6秒で、DBがHealthyになってからアプリが起動する順になりました。ボリュームのマウント先を /var/lib/postgresql にしているのは、postgres:18 のイメージがこのパスをVOLUMEとして宣言しているためです。17以前の記事にある /var/lib/postgresql/data とは違います(変更の詳細は Docker PostgreSQL構築の手順|18で変わったボリューム位置とcompose定義・initdb初期化を実装目線で解説)。古い記事にある先頭の version: "3.9" も不要です。書くと the attribute `version` is obsolete という警告が出るだけです。

depends_onの短縮構文とDBの準備完了待ち

Docker公式の起動順のガイドには「Compose does not wait until a container is “ready”, only until it’s running.」と書かれています。depends_on: [db] の短い書き方では、DBのプロセスが接続を受け付けられる状態になる前にアプリが動き出すことがあります。

DBに届かないままアプリが起動するとどうなるかを確かめました。DBを止めた状態でアプリを起動すると、Spring Data JDBCの jdbcDialect を作る段階で「CannotGetJdbcConnectionException: Failed to obtain JDBC Connection」が出て、プロセスは終了コード1で終わりました。起動時にDBの種類を判定するため、接続できないと起動そのものが失敗します。手元の3回の試行では、たまたまJVMの起動がDBより遅く、短い書き方でも失敗しませんでした。しかし、それはタイミングに頼っているだけです。DBが重いマシンや初回のinitdbでは、順序が逆転しえます。healthcheck と condition: service_healthy をセットで書いておきます。

環境変数によるDB接続先とパスワードの設定

Spring Bootでは、OS環境変数が application.properties の値より優先されます。そのため、検証と本番で同じイメージを使い、値だけを差し替えられます。プロパティ名から環境変数名への変換は、公式が「Replace dots (.) with underscores (_).」「Remove any dashes (-).」「Convert to uppercase.」と定めています。ハイフンはアンダースコアに置き換えるのではなく、削除します。spring.main.log-startup-info は SPRING_MAIN_LOGSTARTUPINFO です。ハイフンをアンダースコアに置き換えると、意図したプロパティへバインドされないことがあります。公式の変換規則に従い、起動後に設定が反映されたことを確認してください。

パスワードは上の例のように env_file で別ファイルから読み込みます。environment に直書きしたままComposeファイルをコミットすると、その値もリポジトリへ入ります。env_file もGit管理から除外する必要があり、コンテナ内では環境変数として渡ります。本番ではCompose secretsなど、ファイルとして秘密情報を渡す方式も検討してください。なお、Initializrで選べる「Docker Compose Support」は、開発中に compose.yaml を自動で起動する機能です。ドキュメントに「Repackaged archives do not contain Spring Boot’s Docker Compose by default.」とあるとおり、本番用のjarには入りません。本番の起動手順の代わりにはならない点に注意してください。

処理中のリクエストを完了させる停止猶予の設計

新しいイメージに入れ替えるたびに、古いコンテナは停止されます。Spring Bootは3.4から、組み込みサーバーのgraceful shutdownが既定で有効です。停止の合図(SIGTERM)を受けると新しいリクエストを断り、処理中のものが終わるのを待ちます。プロパティ一覧では、その待ち時間 spring.lifecycle.timeout-per-shutdown-phase の既定は30秒です。

一方、Docker側の猶予は短く設定されています。docker stop とComposeの stop_grace_period の既定は10秒で、Composeの仕様は「Default value is 10 seconds for the container to exit before sending SIGKILL.」と定めています。そこで、20秒かかるエンドポイントを呼んでいる最中に、2秒後に停止させて結果を比べました。

stop_grace_period リクエストの結果 停止までの時間 終了コード
既定(10秒) 12.4秒で切断 11秒 137(SIGKILL)
40s 20.1秒でHTTP 200 18秒 143(SIGTERM)

どちらのログにも「Commencing graceful shutdown. Waiting for active requests to complete」は出ています。しかし既定の10秒では、Spring Bootが待っている途中でSIGKILLが届きました。40秒にすると「Graceful shutdown complete」まで進み、リクエストは最後まで返りました。猶予は timeout-per-shutdown-phase より長くします。この検証では40秒で完了しました。ただし30秒は停止フェーズごとの上限なので、複数フェーズや終了処理を持つアプリでは停止全体の所要時間を確認し、それを上回る猶予を設定します。docker run なら --stop-timeout 40 で同じ指定ができます。

この仕組みには、停止シグナルがJavaプロセスへ届く必要があります。JavaをPID 1にする構成のほか、シグナルを転送するinitプロセスを使う構成でも動作します。Docker公式のDockerfileリファレンスは、シェル形式のENTRYPOINTについて「your executable doesn’t receive a SIGTERM from docker stop」と注意しています。ENTRYPOINT ["java", ...] のexec形式で書いてください。停止順序の設計全般は グレースフルシャットダウンとは?SIGTERM受信後の停止順序と猶予時間の設計を実装者目線で解説 で扱っています。

停止に合わせて、ロードバランサーから外すタイミングも決めておく必要があります。Spring Boot 4.1.1では management.endpoint.health.probes.enabled の既定がtrueで、Kubernetes以外で動かしても /actuator/health/readiness と /actuator/health/liveness が {"status":"UP"} を返しました。トラフィックの振り分け判定にはreadinessを、回復不能な異常による再起動の判定にはlivenessを使います(livenessとreadinessの使い分けは ヘルスチェックとは?LivenessとReadinessの分離と閾値設計を実装目線で解説)。

レジストリ経由の本番サーバーへの配布

イメージへのタグ付けと配布先での取得

手元やCIで作ったイメージは、レジストリを経由して本番へ運びます。配布先でビルドはしません。以下はローカルに myapp:1.0.0 がある前提です。Dockerfile方式では docker build -t myapp:1.0.0 . で作成し、Buildpacksの既定名を使った場合は docker tag demo:0.0.1-SNAPSHOT myapp:1.0.0 で名前を揃えます。Jibで直接push済みなら、ローカルのtagと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

# 配布先のサーバー(compose.yaml の image を新しいタグに書き換えてから)
# 非公開イメージの場合は、配布先でも read:packages 権限のPAT(classic)でログインする
# echo "$GITHUB_PAT" | docker login ghcr.io -u USERNAME --password-stdin
docker compose pull app
docker compose up -d app

開発機がApple Silicon(arm64)で、本番がx86_64のサーバーという組み合わせは珍しくありません。この場合、手元で普通にビルドしたイメージは本番で「exec format error」となり、起動しません。Dockerfileなら docker build --platform linux/amd64、Buildpacksならプラグインの imagePlatform(例 linux/amd64)で、配布先に合わせて作ります。CIでも、本番と同じCPUアーキテクチャのランナーを使うか、ビルド対象のプラットフォームを明示する必要があります。

GitHub Actionsによるjarビルドとpushの自動化

先にMavenでjarを作り、そのjarをDockerfileでコピーする構成なら、ワークフローは次のようになります。各アクションの版は2026年10月時点の最新メジャーです。

name: release
on:
  push:
    branches: [main]
jobs:
  image:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    services:
      db:
        image: postgres:18
        env:
          POSTGRES_DB: app
          POSTGRES_USER: app
          POSTGRES_PASSWORD: test
        ports:
          - 5432:5432
        options: >-
          --health-cmd "pg_isready -U app -d app"
          --health-interval 5s --health-timeout 3s --health-retries 10
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-java@v6
        with:
          distribution: temurin
          java-version: '25'
          cache: maven
      - run: ./mvnw -B package
        env:
          SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/app
          SPRING_DATASOURCE_USERNAME: app
          SPRING_DATASOURCE_PASSWORD: test
      - uses: docker/login-action@v4
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/setup-buildx-action@v4
      - uses: docker/build-push-action@v7
        with:
          context: .
          push: true
          tags: ghcr.io/example/myapp:${{ github.sha }}

services のPostgreSQLは、mvnw package の中で走るテストの接続先です。ヘルスチェックが通るまで後続のステップは始まりません(サービスコンテナの起動待ちは GitHub Actionsのサービスコンテナ(services)|DB・Redisを立てる結合テストと起動待ちの設計)。もう1つ、要になるのが context: . の1行です。build-push-action は、何も指定しないとGitリポジトリを直接ビルドの入力にします。READMEには「any file mutation in the steps that precede the build step will be ignored」とあり、直前の mvnw package で作った target/*.jar はビルドに渡りません。packages: write は、GITHUB_TOKEN でGHCRへpushするためにGitHub Docsが指定している権限です。キャッシュやマルチアーキテクチャまで含めた設計は GitHub ActionsでDockerイメージをビルドする|push先とキャッシュ・マルチアーキの設計 にあります。

タグ設計とロールバック

本番が参照するタグには、コミットハッシュかリリース番号を使います。latest だけで運用してはいけません。latest は付け替えられるタグで、取得した時点によって中身が変わります。障害が起きたときに「本番で動いていたのはどのビルドか」が分からず、戻し先も指定できません。github.sha でタグを付けておけば、compose.yaml のタグを1つ前のハッシュに戻して docker compose up -d するだけで、アプリのイメージを戻せます。前提は、同じタグを上書きしない運用と、レジストリに古いイメージが残っていることです。DBのスキーマやデータはイメージと一緒には戻らないため、マイグレーションは1つ前の版でも動く形(後方互換)にしておきます。単体のサーバーで足りなくなり、ECSやEKSで無停止更新やオートスケールを組む段階になったら、一創のインフラ構築(AWS・Google Cloud・Azure)でも基盤の設計を支援しています。

コンテナ内のlocalhostとDB接続先の指定

開発中に spring.datasource.url=jdbc:postgresql://localhost:5432/app で動いていたアプリも、コンテナに入れると同じ設定ではDBにつながりません。コンテナの中から見た localhost は、そのコンテナ自身だからです。Composeでは上の例のようにサービス名(db)で指定します。ホスト側で動くDBにつなぐなら、Docker Desktopやcolimaでは host.docker.internal を使います。どちらの場合も、値は環境変数で外から渡します。

よくある質問

Dockerでのデプロイとは何を指しますか?

アプリと実行に必要なものを1つのイメージにまとめ、そのイメージからコンテナを起動して稼働させることを指します。Spring Bootの場合は、実行可能jarを入れたイメージを作ってレジストリにpushし、本番サーバーで docker pull してから docker run か docker compose up で起動するところまでです。

Spring BootのDockerイメージはBuildpacks・Dockerfile・Jibのどれで作るべきですか?

要件が無ければBuildpacksです。非rootでの実行とレイヤー分割が既定で済み、書くファイルもありません。ベースイメージを固定したい、OSパッケージを足したいならDockerfileにします。CIでDockerデーモンを使えないならJibを選び、jib.container.user で非rootにしてください。

docker compose upでSpring BootがDBより先に起動して落ちるのはなぜですか?

depends_on の短い書き方は、DBのコンテナが「running」になるまでしか待たないためです。Spring Data JDBCでは、起動時にDBへ接続できないと「Failed to obtain JDBC Connection」で終了します。DBに pg_isready などの healthcheck を付け、condition: service_healthy を指定してください。

openjdk:11-jre-slimを使い続けても問題ありませんか?

使い続けられません。openjdk イメージはDocker公式で非推奨になっており、2026年10月12日時点で、GA版のタグはレジストリに問い合わせるとHTTP 404を返します。手元にキャッシュが無い環境では、docker build は FROM の行で失敗します。eclipse-temurin:25-jre などに置き換え、jarのビルドに使ったJava以上の版を選んでください。

Spring Bootのlayered jarはDockerでどう使いますか?

Dockerfileのビルドステージで java -Djarmode=tools -jar application.jar extract --layers --destination extracted を実行し、展開された4つのディレクトリを別々にCOPYします。依存ライブラリの層が変わらなければ、再ビルドで作り直されるのはアプリの層だけになります。Buildpacksでは同じ分割が自動で行われます。旧構文の layertools は4.1.0で使えなくなりました。

関連記事

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

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

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

資料請求

RELATED POSTS 関連記事

目次