Spring BootアプリとMySQLをそれぞれ起動していると、接続先や起動順序、DBデータの保存場所を個別に管理する必要があります。Docker Composeを使えば、アプリとDBの構成をcompose.yamlへまとめ、同じコマンドで起動・終了できます。
この記事では、Spring Bootアプリをコンテナで動かし、MySQLへ接続するローカル開発環境を作ります。Dockerfile、環境変数、DBの起動待ち、データの永続化を設定し、最後にアプリからSELECT 1を実行して接続を確かめます。
Spring BootとMySQLをまとめて起動する構成
ローカル開発で行うこととTestcontainersとの役割の違い
用意するのは、HTTPリクエストを受け付けるappサービスと、MySQLを動かすdbサービスです。アプリからはComposeのネットワークを通じてDBへ接続し、MySQLのデータはnamed volumeへ保存します。
この構成は、開発中のアプリを起動して手動で操作する用途に向いています。一方、自動テストの実行に合わせてMySQLを用意し、保存・取得を検証したい場合はTestcontainersが候補になります。Repositoryの自動テストへ進む場合は、次の記事を参照してください。

必要なDocker環境とプロジェクトのファイル構成
Linuxコンテナを実行できるDocker環境と、docker composeコマンドを使えるComposeを用意します。WindowsでDocker Desktopを使う場合も、Linuxコンテナを実行する構成にします。以下のコマンドは、compose.yamlを置いたプロジェクト直下で実行します。
docker version
docker compose versionアプリの例はJava 17、Spring Boot 3.5.16、Mavenを前提にします。Spring Boot 3.5.16はJava 17以上に対応しています(Spring Boot 3.5.16のシステム要件)。ビルド用にはmaven:3.10.0-eclipse-temurin-17-noble(Maven公式イメージ)、実行用にはeclipse-temurin:17-jre-noble(Eclipse Temurin公式イメージ)、DBにはmysql:8.4.11を使います。各イメージの公式タグにあるLinux環境を選び、チーム内でそろえてください。
イメージの指定は後述の.envへ集めます。利用するCPUアーキテクチャに対応した具体的なタグ、またはダイジェストを選び、チーム内でそろえてください。Mavenもコンテナ内で動かすため、この手順だけならホストへのJava・Mavenのインストールは不要です。
compose-demo/
├── pom.xml
├── Dockerfile
├── compose.yaml
├── .env
├── .env.example
├── .gitignore
├── .dockerignore
└── src/
└── main/
└── java/
└── com/example/composedemo/
├── ComposeDemoApplication.java
└── DbCheckController.javaSpring BootアプリとDockerfileを準備する
Mavenの依存関係とDB接続を確認する最小アプリ
Spring Bootの親POMを設定したpom.xmlへ、Web、JDBC、MySQLドライバを追加します。以下はPOM全体ではなく、対応する要素へ組み込む断片です。同じ依存関係やプラグインがある場合は重複させず、既存の定義へ合わせます。
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<finalName>app</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>finalNameをappにすることで、Dockerfileからコピーする実行可能JARの名前をtarget/app.jarへそろえます。この例は単一モジュールのJARプロジェクトが対象です。
ComposeDemoApplication.javaには、起動用クラスを置きます。
package com.example.composedemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ComposeDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ComposeDemoApplication.class, args);
}
}同じパッケージへDbCheckController.javaを作ります。HTTPリクエストを受けたタイミングでSQLを実行するため、アプリの起動とDB接続の成功を分けて確認できます。
package com.example.composedemo;
import java.util.Map;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DbCheckController {
private final JdbcTemplate jdbc;
public DbCheckController(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
@GetMapping("/db-check")
public Map<String, Integer> check() {
Integer value = jdbc.queryForObject("SELECT 1", Integer.class);
return Map.of("value", value);
}
}テーブル作成は不要です。ここで確認するのは、アプリのDataSourceからMySQLへ接続してSQLを実行できることです。業務テーブルへの権限や、データの保存・取得までは確認しません。
MavenでビルドしEclipse Temurinで実行するDockerfile
Dockerfileは、MavenでJARを作る段階と、Eclipse TemurinでJARを実行する段階に分けます。イメージ名はComposeからビルド引数で渡します。
ARG MAVEN_IMAGE
ARG RUNTIME_IMAGE
FROM ${MAVEN_IMAGE} AS build
WORKDIR /workspace
COPY pom.xml ./
COPY src ./src
RUN mvn -B package
FROM ${RUNTIME_IMAGE}
WORKDIR /app
COPY --from=build /workspace/target/app.jar ./app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]実行用イメージへコピーするのはJARだけです。EXPOSE 8080はコンテナが使用するポートを示すメタデータで、ホストへの公開はComposeのportsで設定します。MySQLのパスワードをビルド引数へ渡す必要はありません。接続情報は、コンテナを起動するときの環境変数で渡します。
RUN mvn -B packageはイメージのビルド中に実行されます。この段階では、Composeのdbサービスへの接続を前提にできません。既存プロジェクトにDBへ接続するテストがある場合は、そのテストの実行環境を別途整えます。
.gitignoreと.dockerignoreで.envの混入を防ぐ
.gitignoreには、認証情報を保存する.envと派生ファイルを追加します。共有する.env.exampleには、変数名と秘密情報を含まない設定だけを残します。
.env
.env.*
!.env.example
target/.dockerignoreでも除外し、Dockerのビルドコンテキストへ送らないようにします。
.git
.env
.env.*
target/.gitignoreは、すでにGitで追跡しているファイルには効きません。認証情報をコミットした場合は、追跡対象から外すだけで済ませず、その認証情報を変更してください。また、.dockerignoreはComposeがホスト上の.envを読むことを妨げません。
compose.yamlにアプリとMySQLを定義する
app・dbサービスとnamed volumeを設定する
プロジェクト直下のcompose.yamlへ、次の構成を記述します。${変数名:?メッセージ}は、値が未設定または空の場合に処理を止めるための指定です。必要な値は次の章で.envへ設定します。
services:
app:
build:
context: .
args:
MAVEN_IMAGE: "${MAVEN_IMAGE:?Set MAVEN_IMAGE}"
RUNTIME_IMAGE: "${RUNTIME_IMAGE:?Set RUNTIME_IMAGE}"
ports:
- "127.0.0.1:8080:8080"
environment:
SPRING_DATASOURCE_URL: "jdbc:mysql://db:3306/${MYSQL_DATABASE:?Set MYSQL_DATABASE}"
SPRING_DATASOURCE_USERNAME: "${MYSQL_USER:?Set MYSQL_USER}"
SPRING_DATASOURCE_PASSWORD: "${MYSQL_PASSWORD:?Set MYSQL_PASSWORD}"
depends_on:
db:
condition: service_healthy
db:
image: "${MYSQL_IMAGE:?Set MYSQL_IMAGE}"
environment:
MYSQL_DATABASE: "${MYSQL_DATABASE:?Set MYSQL_DATABASE}"
MYSQL_USER: "${MYSQL_USER:?Set MYSQL_USER}"
MYSQL_PASSWORD: "${MYSQL_PASSWORD:?Set MYSQL_PASSWORD}"
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?Set MYSQL_ROOT_PASSWORD}"
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test:
- CMD-SHELL
- >-
MYSQL_PWD="$${MYSQL_PASSWORD}"
mysql --protocol=TCP --host=127.0.0.1
--user="$${MYSQL_USER}"
--database="$${MYSQL_DATABASE}"
--execute="SELECT 1" > /dev/null 2>&1
interval: 10s
timeout: 5s
retries: 12
start_period: 30s
volumes:
mysql_data:mysql_dataはMySQLのデータディレクトリへ接続するnamed volumeです。コンテナの書き込み領域とは別に保存されるため、コンテナを削除しても、volumeを残せばDBデータを再利用できます。
アプリには通常のDBユーザーだけを渡し、rootパスワードはdbサービスだけに設定しています。アプリのHTTPポートはホストの127.0.0.1へ公開し、ローカルからアクセスする構成です。
コンテナ間はdbへ接続し、ホスト公開ポートと区別する
Composeの同じネットワークに参加するサービスは、サービス名で接続できます(Docker Composeのネットワーク)。appからMySQLへ接続する場合は、ホスト名をdb、ポートをMySQLコンテナ内の3306にします。
| アクセス元 | アクセス先 | 指定する接続先 |
|---|---|---|
| ホストのブラウザー | app | http://localhost:8080/db-check |
| appコンテナ | dbコンテナ | db:3306 |
| ホストのDBクライアント | dbコンテナ | DBポートを公開した場合のホスト側ポート |
appコンテナ内のlocalhostは、そのアプリコンテナ自身を指します。ホストPCや別のDBコンテナを指すわけではありません。
主手順ではDBのポートをホストへ公開していません。ホスト上のDBクライアントから接続する必要がある場合だけ、dbサービスへ次の設定を追加します。
ports:
- "127.0.0.1:3307:3306"この場合、ホストからは127.0.0.1:3307へ接続します。アプリからの接続先は引き続きdb:3306です。ホスト側の公開ポートを変えても、コンテナ間の接続ポートは変わりません。
healthcheckとservice_healthyでDBの準備を待つ
depends_onで依存先を列挙するだけでは、MySQLがSQLを実行できる状態になるまで待てません(Composeの起動順序とhealthcheck)。そこでDBにhealthcheckを設定し、アプリ側の起動条件をservice_healthyにしています。
このhealthcheckは、DBコンテナ内からTCPでMySQLへ接続し、アプリ用ユーザーで対象DBのSELECT 1を実行します。プロセスの存在だけではなく、認証してSQLを実行できるかを判定します。TCPを指定することで、Unixソケットだけで動く初期化中のサーバーを成功判定に使わない構成です。
コマンド内の$$は、Composeによる変数展開を避け、コンテナ内のシェルで環境変数を展開させるための記述です。パスワードはMySQLクライアントのMYSQL_PWDへ渡し、コマンド引数へ直接埋め込まない形にしています。ただし、コンテナの環境変数そのものを秘匿する仕組みではありません。
判定間隔は10秒、1回のタイムアウトは5秒、失敗回数は12回にしています。start_periodの30秒は起動直後の失敗を猶予する設定で、必ず30秒待つという意味ではありません。初期化に必要な時間に合わせて調整します。
DBのhealthyは、appからの接続成功まで保証しません。また、起動後にDBが停止しても、この依存条件だけでappが自動的に再起動されるわけではありません。アプリ経由のSQL実行は別に確認します。
.envの補間とコンテナへの環境変数注入を整理する
.envでComposeの値を補間する
.env.exampleを次の形で用意し、.envへコピーします。空欄のイメージ指定とパスワードを設定してから起動してください。MavenイメージにはJava 17を含むもの、実行用にはEclipse TemurinのJava 17、MySQLには8.4系を指定します。
MAVEN_IMAGE=maven:3.10.0-eclipse-temurin-17-noble
RUNTIME_IMAGE=eclipse-temurin:17-jre-noble
MYSQL_IMAGE=mysql:8.4.11
MYSQL_DATABASE=compose_demo
MYSQL_USER=app_user
MYSQL_PASSWORD=
MYSQL_ROOT_PASSWORD=MAVEN_IMAGEとRUNTIME_IMAGEには、Java 17でビルド・実行する公式イメージを指定しています。MYSQL_IMAGEはMySQL 8.4のパッチバージョンを指定しています。rootとアプリ用ユーザーには別々のパスワードを設定します。
.envの役割は、Composeがcompose.yaml内の${MYSQL_DATABASE}などを補間するときに値を提供することです。ファイルへ書いた変数が、すべてのコンテナへ自動的に渡されるわけではありません(Composeの変数補間)。
同名の変数がコマンドを実行するシェルに設定されていると、補間にはシェル側の値が優先されます。.envを直しても設定が変わらない場合は、シェル側に同名の変数が残っていないか確認します。値に$などを含める場合は、.env内での補間を避けるための単一引用符など、ファイルの引用ルールにも注意してください。
environmentでSpring Bootへ設定を渡す
補間した値をコンテナへ渡すのが、サービスごとのenvironmentです。アプリ側では、Spring Bootが読み取る環境変数名へ対応付けています(Spring Bootの外部設定)。
| appへ渡す環境変数 | Spring Bootの設定 |
|---|---|
| SPRING_DATASOURCE_URL | spring.datasource.url |
| SPRING_DATASOURCE_USERNAME | spring.datasource.username |
| SPRING_DATASOURCE_PASSWORD | spring.datasource.password |
この例では、DB接続情報をapplication.propertiesやapplication.ymlへ重ねて書く必要はありません。MySQL側の初期化設定とアプリ側の接続設定に同じ補間元を使い、値の食い違いを減らしています。
既存アプリでは、profile、起動引数、独自のDataSource設定なども確認します。環境変数を渡したのに想定した接続先にならない場合は、設定の上書きと読み込み先を扱う次の記事が補足になります。

env_fileを使う場合との違いを押さえる
サービスのenv_fileは、指定したファイルからコンテナへ環境変数を渡すための設定です。Composeの補間に使う.envとは役割が異なります。
| 仕組み | 主な役割 |
|---|---|
| .env | Composeファイル内の変数補間に値を提供する |
| environment | サービスへ渡す環境変数を明示する |
| サービスのenv_file | ファイルに列挙した環境変数を、そのサービスへ渡す |
たとえばappでenv_fileを使うなら、そのファイルにはSPRING_DATASOURCE_URLなど、アプリが読む名前で値を書きます。MYSQL_PASSWORDという名前で渡すだけでは、この例のSpring Bootの接続パスワードにはなりません。
同じ変数をenv_fileとenvironmentの両方に設定すると、environment側が優先されます。本記事では設定元を追いやすくするため、主手順を.envの補間とenvironmentの明示的な注入に統一しています。代替方式へ変更する場合も、rootパスワードを含むファイルをappへ丸ごと渡さないようにします。
Docker Composeで起動して接続を確認する
docker compose configで設定を確認する
ファイルがそろったら、まず設定の構文と必須変数を確認します。--quietを付けると、解決した設定全体を表示せずに検証できます。
docker compose config --quiet補間後の値やインデントの解釈まで確認する場合は、docker compose configを実行します。これはCompose設定の確認であり、イメージの取得可否やMySQLへの接続成功を確認するものではありません。
docker compose configの展開結果にはパスワードが含まれます。出力をそのままGit、チャット、問い合わせ、画面キャプチャへ載せないでください。共有が必要なら、パスワードなどを伏せた必要部分だけを抜き出します。
docker compose up –buildでアプリとMySQLを起動する
次のコマンドでアプリのイメージをビルドし、アプリとMySQLを起動します。-dはバックグラウンドで動かす指定です。
docker compose up --build -d最初の起動では、イメージやMaven依存関係の取得、MySQLのデータディレクトリの初期化が必要です。DBのhealthcheckが成功すると、依存条件を満たしたappが起動します。コマンドが終了した後も、アプリがHTTPリクエストを受けられる状態になったかは次の手順で確認します。
このDockerfileはソースをイメージへコピーする方式です。Javaコードを変更したら、同じup --buildで再ビルドします。ホストのソース変更が実行中のアプリへ即時反映される構成ではありません。
docker compose ps・logsとアプリの操作でDB接続を確認する
サービスの状態と直近のログを確認します。
docker compose ps -a
docker compose logs --tail=100 db appDBがhealthyで、appが終了していないことを確認したら、ブラウザーでhttp://localhost:8080/db-checkを開きます。成功時に期待する応答は{"value":1}です。これは掲載したControllerがMySQLへSELECT 1を実行した結果に対応します。
HTTP接続自体が失敗する場合はappの起動状態とポート、HTTP応答がエラーになる場合はappの例外ログを確認します。起動ログが出ているだけでは、DB接続まで成功したとは判断しません。
この確認用エンドポイントには認証処理を設けていません。ローカルの接続確認に使い、外部へ公開するアプリへそのまま持ち込まないようにしてください。
MySQLの初期化とデータを残す停止方法
初期化環境変数は既存volumeへ再適用されない
MySQL公式イメージのMYSQL_DATABASE、MYSQL_USER、MYSQL_PASSWORD、MYSQL_ROOT_PASSWORDは、空のデータディレクトリを初期化するときに使う設定です。すでに初期化済みのデータがある場合、環境変数を書き換えてもDBやユーザーがその内容へ作り直されるわけではありません。
たとえば、最初の起動後に.envのMYSQL_PASSWORDだけを変更すると、再作成されたappは新しい値で接続しようとします。一方、volume内のMySQLユーザーは以前のパスワードのままなので、認証に失敗します。このhealthcheckも同じ変数を使うため、DBがunhealthyになる可能性があります。
既存データを維持するなら、MySQL内のユーザー設定とアプリ側の接続設定をそろえて変更します。.envの編集は、DB内のパスワード変更操作の代わりにはなりません。
docker compose downで終了し、named volumeを残す
通常の開発終了では、次のコマンドを使います。
docker compose downコンテナとComposeが作成したネットワークを削除し、named volumeは残します。次回、同じComposeプロジェクトとして起動すれば、保存済みのMySQLデータを再利用できます。コンテナを削除せず一時停止したい場合は、docker compose stopも使えます。
volumeの実際の名前には、通常、Composeのプロジェクト名が付きます。ディレクトリ名や-pによるプロジェクト名を変えると、別のvolumeが作られて空のDBに見えることがあります。データが見えなくなった場合は、削除したと判断する前に接続中のvolumeを確認してください。
なお、SELECT 1はデータを保存しません。永続化を確認する場合は、専用の確認用テーブルへ識別できる行を保存し、通常のdownと再起動を挟んで同じ行を読み出します。接続確認とデータ保持の確認は分けて行います。
データを削除する前に対象volumeと退避の要否を確認する
DBを初期状態から作り直す場合も、先に削除対象を特定します。起動中のDBコンテナのIDを確認し、そのコンテナのマウント情報を調べます。
docker compose ps -q db次のDB_CONTAINER_IDを表示されたIDへ置き換えます。Mountsのうち、Destinationが/var/lib/mysqlになっている項目のNameが対象volumeです。
docker inspect --format '{{json .Mounts}}' DB_CONTAINER_ID必要なテーブル、投入したデータ、ユーザー設定などが残っていないか確認し、必要ならダンプなどで退避します。退避先は削除対象のvolumeの外に置き、復元に必要な内容を確保してから削除へ進みます。
docker compose down --volumesは、通常の終了操作と異なり、このComposeで管理するnamed volumeも削除します。本記事の構成ではmysql_dataに保存したDB、テーブル、行、ユーザー設定が失われます。認証エラーを解消するためだけに実行しないでください。
構成へ別のvolumeを追加している場合は、--volumesの削除範囲も広がります。DBのvolumeだけを削除するなら、通常のdownで終了した後、確認済みのvolume名を指定して個別に削除する方法を選びます。対象を確認せずにvolumeを一括削除する操作は避けてください。
起動・接続に失敗したときの確認ポイント
接続先・ポート・環境変数の渡し方を確認する
最初に、どこからどこへ接続しているかを確認します。appから接続するDBのホスト名とポートはdb:3306です。ホスト側に公開した3307や、app自身を指すlocalhostへ変更しないようにします。
| 確認する項目 | 見直す内容 |
|---|---|
| アプリへのHTTP接続 | appの起動状態、ホスト側8080の競合、ブラウザーのURL |
| JDBC URL | サービス名db、コンテナ側3306、対象DB名 |
| 環境変数 | .envの補間元と、appのenvironmentへの対応付け |
| 設定変更の反映 | コンテナを再作成したか。restartだけでは環境変数は更新されない |
| 既存アプリの設定 | 起動引数、profile、独自DataSourceなどの影響 |
イメージの取得やビルドで失敗した場合は、DB接続より前の段階です。指定したタグ、CPUアーキテクチャ、レジストリへの到達、Mavenの依存解決、JARの出力名を確認します。
DBの健康状態と初期化ログを確認する
appが起動しない場合は、依存先のDBがhealthyになっているかを確認します。
docker compose ps -a
docker compose logs --tail=200 db確認するのは、初期化処理が進んでいるか、MySQLが終了していないか、ディスク容量やデータディレクトリの問題がないかです。既存データと異なるMySQLの系列を指定していないかも確認します。原因を見ずにhealthcheckの待ち時間だけを延ばしても、認証やデータ互換性の問題は解消しません。
掲載したhealthcheckは出力を捨てるため、失敗理由の詳細は表示されません。MySQLが起動している場合は、次のようにパスワードを対話入力して接続を切り分けられます。DB名やユーザー名を変えた場合は、その値へ置き換えます。
docker compose exec db mysql --protocol=TCP --host=127.0.0.1 --user=app_user --password --database=compose_demo --execute="SELECT 1"これはDBコンテナ内からの確認です。ここで成功しても、appの接続設定が正しいことまでは確認できないため、最後は/db-checkでもSQLを実行します。ログを共有するときは、接続情報や認証情報が含まれていないか確認してください。
.env変更後の認証エラーと既存データを切り分ける
.envの変更をきっかけに認証が失敗した場合は、まず既存volumeを使っているか、DBを最初に初期化したときからユーザー名やパスワードを変えていないかを確認します。
誤って接続設定だけを変更したなら、DB内の設定と一致する値へ戻します。パスワードを変更する意図があるなら、既存の管理権限でMySQL内のユーザー設定を変更し、その値へアプリとhealthcheckの設定をそろえます。変更した環境変数は、docker compose up -dなどで対象コンテナへ反映します。
データを捨ててよい場合に限り、対象volumeと退避の要否を確認したうえで再初期化を選びます。認証情報の不一致と、データを削除する判断は分けて扱いましょう。
まとめ
Spring BootとMySQLをDocker Composeでまとめると、アプリのビルド、DBの起動待ち、接続設定、データの保存先を一つの構成として管理できます。アプリからはサービス名dbとコンテナ側ポート3306へ接続し、ホストから使う公開ポートと区別します。
.envはComposeの補間、environmentはコンテナへの設定注入を担当します。起動時はhealthcheckとservice_healthyでDBの準備を待ち、その後にアプリからSQLを実行して接続を確認してください。
通常の終了はdocker compose downで行い、named volumeを残します。既存DBの認証情報は.envの編集だけでは変わりません。接続設定とDB内の状態を切り分け、データ削除は対象と退避の要否を確認してから行います。


コメント