はじめに
今回は、Spring BootとPostgreSQL、Dockerを組み合わせてアプリを動かすハンズオンを行います。
最初は単純なHello Worldアプリをコンテナで動かし、その後、PostgreSQLと連携したユーザー登録アプリまで拡張していきます。
Docker
Dockerの概要
Dockerはアプリやその動作に必要な環境(OS やライブラリ、ランタイム、データベースなど)を「コンテナ」と呼ばれる単位でまとめて実行できる仕組みです。
コンテナ内でアプリを動かすため、ローカル環境を汚さずに様々なソフトウェアや設定を試すことができます。
従来のやり方
通常、アプリを動かす場合は自分の PC に Java やライブラリ、ランタイム、データベースなどの環境を直接インストールする必要があります。
しかし、この方法だと以下のような課題があります:
- 環境構築に時間がかかる
- 他のアプリやプロジェクトに影響を与えてしまう
- チームで同じ環境を再現するのが大変
Docker を使うとどうなるか
Docker を使うと、アプリごとに必要な環境をコンテナとしてまとめて管理できるため、
- ローカル環境を汚さずに安全に試せる
- チームの誰でも同じ環境で動かせる
- 短時間で環境を作り直したり、別バージョンを試したりできる
つまり、面倒な環境構築や依存関係のトラブルを気にせず、アプリ開発や学習に集中できるのです。
個人的に、過去にライブラリのインストールが増えすぎて環境が複雑になった経験があり、Dockerの有用性が実感できます。
ここまで書きましたが、イマイチ理解できてないという方は、こちらの記事の説明がわかりやすいと思うので、参考にしてみてください。
ここからのハンズオンでは、Docker上でSpring BootアプリとDBを動かせる環境を構築できます。
まずは手を動かして、Docker での開発・環境構築の流れを体験してみましょう。
【STEP1】 Spring Boot × Docker
まずはSpring Bootで簡単なプロジェクトを作成し、Dockerを使って動かしてみます。
mavenプロジェクトを作成します。
構成はこんな感じです。
spring-test/
├── src/
├── target/
├── pom.xml
└── Dockerfile
Hello Worldを返す簡単なコントローラーを実装しましょう。
コンテナ上での稼働確認が目的なので、簡単なもので結構です。
package com.example.sample;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/")
public String hello() {
return "Hello World!";
}
}
ビルドします。
今回はテストは不要なのでskipで大丈夫です。
$ ./mvnw clean package -DskipTests
続いて、Dockerfileを作成します。
ここでの定義を元にコンテナが作成されるので、重要な部分です。
Javaのインストールや作成したjarのコピー、実際のアプリ起動コマンドについて定義します。
# Javaランタイム
FROM eclipse-temurin:17-jre
# 作業ディレクトリ
WORKDIR /app
# jarをコピー
COPY target/sample-0.0.1-SNAPSHOT.jar app.jar
# アプリ起動
ENTRYPOINT ["java", "-jar", "app.jar"]
# 外部公開ポート
EXPOSE 8080
ここまででSpringをコンテナ上で動かす準備は完了です。
ここから、コンテナの作成や起動を行って稼働確認を行っていきます。
まずは、先ほど作成したDockerfileを元にコンテナイメージを作成します。
イメージ名は"spring-hello"にしておきます。
$ docker build -t spring-hello .
作成できたか確認してみましょう。
docker imagesでイメージの一覧を出力することができます。
"spring-hello"のイメージが作成されていることが確認できます。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
spring-hello latest bdbabb160fec About a minute ago 765MB
では次に、作成したイメージを使ってコンテナを起動してみます。
$ docker run -p 8080:8080 spring-hello
http://localhost:8080/ にアクセスしてみましょう。
Hello World!が表示されますね。

docker psで起動中のプロセスも確認してみます。
このコマンドでは、起動中のコンテナを確認することができます。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b44f7e4d7377 spring-hello "java -jar app.jar" 27 seconds ago Up 27 seconds 0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp vibrant_thompson
無事動いていることが確認できたので、Ctrl+cでプロセスを終了します。
もう一度docker psでプロセスを確認します。
停止されたので、一覧からはなくなっていますね。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
では停止したコンテナは無くなったのかというと、そうではありません。
docker ps -aで停止中のコンテナ含め確認することができます。
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b44f7e4d7377 spring-hello "java -jar app.jar" 2 minutes ago Exited (130) About a minute ago vibrant_thompson
停止したコンテナを再開したい場合はdocker start {CONTAINER ID}で再開できます。
今回このコンテナはもう不要なので、docker rm {CONTAINER ID}でコンテナを削除します。
$ docker rm b44f7e4d7377
b44f7e4d7377
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
spring-hello latest 5bd9d09da8f9 7 minutes ago 705MB
最後に、コンテナイメージも不要なので削除しておきます。
コンテナイメージの削除はdocker rmi {イメージ名}で行えます。
$ docker rmi spring-hello
Untagged: spring-hello:latest
Deleted: sha256:5bd9d09da8f9e8848e3aff0340286cd9032aba573779dfd4157a1c57e3c2b8f0
削除が完了しました。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
【STEP2】 Spring Boot × Docker × PostgreSQL
続いて、コンテナ上にDBも乗せて、ユーザーの登録・取得を行うアプリを作成してみましょう。
また、先ほどはMavenビルドをローカルで行いましたが、そちらもコンテナ上で行ってみます。
db-test/
├── backend/
│ ├── src/
│ ├── target/
│ ├── Dockerfile
│ └── pom.xml
└── docker-compose.yml
コントローラです。
ユーザーの一覧取得、登録を行います。
package com.example.sample.controller;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.sample.entity.AppUser;
import com.example.test.repository.UserRepository;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@RestController
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository){
this.userRepository = userRepository;
}
@GetMapping("/get")
public List<AppUser> getUsers() {
return userRepository.findAll();
}
@PostMapping("/register")
public AppUser registerUser(@RequestBody AppUser user) {
return userRepository.save(user);
}
}
DBのテーブルに対応したエンティティクラスです。
package com.example.sample.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class AppUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String username;
private String email;
public AppUser(){}
public AppUser(String username, String email){
this.username = username;
this.email = email;
}
public Long getId() {
return id;
}
public String getUsername(){
return this.username;
}
public void setUsername(String username){
this.username = username;
}
public String getEmail(){
return this.email;
}
public void setEmail(String email){
this.email = email;
}
}
ユーザー情報を操作するためのデータアクセス層です。
Spring Data JPAを利用するので、SQL を書かずにAppUserのCRUD操作を行うことができます。
package com.example.sample.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.sample.entity.AppUser;
public interface UserRepository extends JpaRepository<AppUser, Long> {
}
DB接続のために設定ファイルも記載します。
DBの接続情報は環境変数にしておいて、具体的な値は後述のdocker-compose.ymlに定義します。
spring.application.name=sample
spring.datasource.url=${SPRING_DATASOURCE_URL}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
ここまでがSpringの実装になります。
続いて、Dockerfileを作成します。
【STEP1】ではビルド済みのjarを置いていましたが、今回はビルドもコンテナを使って行います。
## ビルド用コンテナ
# Maven + Javaランタイム(ビルド専用)
FROM maven:3.9.6-eclipse-temurin-17 AS build
# 作業ディレクトリ
WORKDIR /app
# pom.xml とソースコードをコピー
COPY pom.xml .
COPY src ./src
# jar をビルド(テストはスキップ)
RUN mvn clean package -DskipTests
## 実行用コンテナ
# Javaランタイム(実行専用)
FROM eclipse-temurin:17-jre
# 作業ディレクトリ
WORKDIR /app
# ビルド済み jar をコピー
COPY --from=build /app/target/*.jar app.jar
# アプリ起動
ENTRYPOINT ["java", "-jar", "app.jar"]
# 外部公開ポート
EXPOSE 8080
【STEP1】ではイメージのビルドやコンテナの起動を個別で行っていましたが、今回はDBの起動も行います。そうした中で毎回コマンドを打たなければいけないのは面倒なので、複数のコンテナを一括で定義・起動・管理するための設定ファイルdocker-compose.ymlを使用します。
面倒さ回避だけでなくチーム開発での再現性や、環境変数やポート番号の一元管理などの管理性の向上も目的です。
version: "3.9"
services:
db:
image: postgres:15 # PostgreSQL 公式イメージを使用
container_name: my-postgres # コンテナ名
environment:
POSTGRES_USER: appuser # DB情報
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
ports:
- "5432:5432" # ホストの5432 → コンテナの5432
volumes:
- db_data:/var/lib/postgresql/data # データ格納先
networks:
- app-network
app:
build:
context: ./backend # backend/ 以下をビルドコンテキストに
dockerfile: Dockerfile # Dockerfileを指定
container_name: my-spring-app
depends_on:
- db # db が先に起動してから app を起動
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/appdb
SPRING_DATASOURCE_USERNAME: appuser
SPRING_DATASOURCE_PASSWORD: secret
ports:
- "8080:8080" # ホスト8080 → コンテナ8080
networks:
- app-network
volumes:
db_data: #DBのデータを永続化するボリューム
networks:
app-network: # アプリとDB用の専用ネットワークを作成
ここまで完了したら、いよいよ起動します。
docker-composeコマンドを使用します。
$ docker-compose up --build
--build はイメージを初回ビルドするときだけでOKです。
起動できたら、ターミナルからリクエストを送ってみましょう。
$ curl -X POST http://localhost:8080/register -H "Content-Type: application/json" -d '{"username":"Taro","email":"taro@example.com"}'
{"id":1,"username":"Taro","email":"taro@example.com"}
$ curl http://localhost:8080/get
[{"id":1,"username":"Taro","email":"taro@example.com"}]%
DBに登録できていることが確認できました。
レコードはDBに保存されているので、Ctrl+cでプロセスを終了し、再度コンテナを立ち上げても残っています。
既存のボリュームを削除するにはこちらのコマンドを実行します。
docker-compose down -v
おわりに
以上で、Spring BootとPostgreSQLをDocker上で動かす基本的なハンズオンは完了です。
今回の手順を通して、以下のポイントを体験できました:
- Dockerfile を使ったアプリのコンテナ化
- Docker Compose によるアプリと DB の一括管理
- コンテナ間通信(Spring Boot と PostgreSQL)の確認
- 永続化ボリュームの活用と再現性のある開発環境構築
今回の内容はあくまで基本形ですが、ここからさらに REST API を増やしたり、フロントエンドと連携したりと応用していくことも可能です。
Docker を使うことで、ローカル環境を汚さずに安全に試せるので、ぜひ自分でも色々な設定やアプリを試してみてください。