1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring Boot × Docker × PostgreSQL で学ぶ簡単ハンズオン

Posted at

はじめに

今回は、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を返す簡単なコントローラーを実装しましょう。
コンテナ上での稼働確認が目的なので、簡単なもので結構です。

HelloController.java
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!が表示されますね。
image.png

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

コントローラです。
ユーザーの一覧取得、登録を行います。

UserController.java
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のテーブルに対応したエンティティクラスです。

AppUser.java
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操作を行うことができます。

UserRepository.java
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に定義します。

application.properties
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を使用します。
面倒さ回避だけでなくチーム開発での再現性や、環境変数やポート番号の一元管理などの管理性の向上も目的です。

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 を使うことで、ローカル環境を汚さずに安全に試せるので、ぜひ自分でも色々な設定やアプリを試してみてください。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?