Docker 環境で Spring Boot Webサービスを起動する

# Docker 環境で Spring Boot Webサービスを起動する

こんにちは、@studio_meowtoon です。今回は、WSL Ubuntu 24.04 の Docker 環境で Spring Boot Web アプリケーションをコンテナーとして起動する方法を紹介します。
![spring-boot_on_docker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/39342ee0-9096-7800-e1d1-23d209527b7c.png)

## 目的

Windows 11 の Linux でクラウド開発します。

こちらから記事の一覧がご覧いただけます。

https://qiita.com/studio_meowtoon/items/6119ab05d8b13527efd8

## 実現すること

ローカル環境の Ubuntu の Docker 環境で、Dockerfile からビルドした Spring Boot Web サービスのカスタムコンテナーを起動します。

**JAR ファイル形式のアプリをコンテナーとして起動**
```mermaid
flowchart LR
    terminal --> app[app.jar]
    subgraph Ubuntu
        subgraph Docker
            subgraph container[app-hello-spring-boot]
                subgraph JVM
                    subgraph app[app.jar]
                        tomcat
                    end
                end
            end
        end
    end
style Ubuntu fill:#FFBCA4, stroke:#E0460F
style Docker fill:#EEFDFF, stroke:#74B9DC
style JVM fill:#DCFFDC, stroke:#2F872F
style container fill:#FFD7E4, stroke:#E13C73
style app fill:#ECECFF, stroke:#9674DC
style tomcat fill:#FFFFDE, stroke:#B4AE43
```

**実行環境**

|要素|概要|
|:--|:--|
|terminal|ターミナル|
|Ubuntu|OS|
|Docker|コンテナー実行環境|

**Web サービス コンテナー**

|要素|概要|
|:--|:--|
|app-hello-spring-boot|カスタムコンテナー|
|JVM|Java 実行環境|
|app.jar|Java アプリケーション|
|tomcat|Web サーバー|

:::note alert
**Spring Boot** では、**コマンド**で**簡単にコンテナイメージを作成できる機能**があります。しかし、この記事では Dockerfile を使用してコンテナイメージをビルドすることで、Docker の基礎的な使い方を学習することを目的としています。ご注意ください。
:::

## 技術トピック

### Dockerfile とは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

### [Dockerfile](https://docs.docker.jp/engine/reference/builder.html#dockerfile)

:::note info
Dockerfile は、Docker コンテナーを構築するためのテキストファイルです。Docker コンテナーはアプリケーションやサービスを実行するための環境を含む軽量でポータブルな仮想化ユニットです。

|キーワード|内容|
|:--|:--|
|スクリプト形式|Dockerfile はシンプルなスクリプト形式で記述されるため、コンテナーのビルドプロセスを自動化することが容易です。|
|レイヤー構造|Docker イメージは Dockerfile の各命令が実行される際にレイヤーとして生成され、再利用やキャッシュが可能な構造となっています。|
|バージョン管理|Dockerfile はテキストベースであるため、コードと同様にバージョン管理システムで管理しやすいです。|
|ポータビリティ|Dockerfile により、アプリケーションとその依存関係が1つのコンテナイメージにパッケージ化されるため、異なる環境間での移植性が高まります。|
|自動化と効率化|Dockerfile を使用することで、アプリケーションのビルドや環境構築を自動化できます。これにより、手動での作業時間やヒューマンエラーが減り、開発・デプロイプロセスが効率的になります。|
|再現性|Dockerfile はビルド手順を完全に定義するため、異なる環境で同じアプリケーションを再現できます。これにより、開発、テスト、本番環境間での一貫性が確保されます。|
|環境の分離|Docker コンテナーはホストシステムから分離されるため、アプリケーションの依存関係やライブラリの衝突を回避し、より安全な環境で実行できます。|
|拡張性|Dockerfile を使用することで、カスタムイメージを作成できます。このため、特定のニーズに合わせてカスタマイズされたコンテナイメージを容易に作成できます。|
:::

</details>

## 開発環境

+ Windows 11 Home 23H2 を使用しています。  

:::note info
[WSL](https://learn.microsoft.com/ja-jp/windows/wsl/about) の Ubuntu を操作しますので macOS の方も参考にして頂けます。  
:::

<details>

WSL (Microsoft Store アプリ版) [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
> wsl --version
WSL バージョン: 2.2.4.0
カーネル バージョン: 5.15.153.1-2
WSLg バージョン: 1.0.61
```

Ubuntu [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04 LTS
Release:        24.04
Codename:       noble
```

Java JDK [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/4d11e94a2389758759cd)

```sh
$ java -version
openjdk version "11.0.24" 2024-07-16
OpenJDK Runtime Environment (build 11.0.24+8-post-Ubuntu-1ubuntu324.04.1)
OpenJDK 64-Bit Server VM (build 11.0.24+8-post-Ubuntu-1ubuntu324.04.1, mixed mode, sharing)
```

Maven [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/f4cdc4039158153365fb)

```sh
$ mvn -version
Apache Maven 3.8.7
Maven home: /usr/share/maven
Java version: 11.0.24, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
```

Docker [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/3bceee25ac67cc73ebbe)

```sh
$ docker --version
Docker version 27.0.3, build 7d4bcd8
```

</details>

:::note warn
この記事では基本的に **Ubuntu のターミナルで操作**を行います。**Vim を使用してコピペする方法**をはじめて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。
:::

https://qiita.com/studio_meowtoon/items/7771ad48676926e41463

## 作成する Web アプリケーションの仕様

|No|エンドポイント|HTTPメソッド|MIME タイプ|
|:--|:--|:--|:--|
|1|/api/data|GET|application/json|

:::note info
**/api/data** というエンドポイントに対して HTTP GET リクエストを送信すると、JSON データがレスポンスされるシンプルな Web サービスを実装します。
**{"message":"Hello World!"}**
:::

## Hello World を表示する手順

### Spring Boot Web サービスの作成

こちらの関連記事で手順がご確認いただけます。

https://qiita.com/studio_meowtoon/items/37ac0082a3228e4de570

### プロジェクトフォルダーに移動

プロジェクトフォルダーに移動します。  
※ ~/tmp/hello-spring-boot をプロジェクトフォルダーとします。  

```sh
$ cd ~/tmp/hello-spring-boot
```

<details>
<summary>構成を単純にする為に、全ての要素をアプリケーションクラス記述した例</summary>

コントローラークラスを削除して、アプリケーションクラスを修正します。

```sh
$ rm src/main/java/com/example/springboot/controller/HelloController.java
$ vim src/main/java/com/example/springboot/Application.java
```

ファイルの内容

```java:Application.java
package com.example.springboot;

import java.util.Map;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
@RequestMapping("/api")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @GetMapping("/data")
    public Map<String, String> getData() {
        Map<String, String> map = Map.of("message", "Hello World!");
        return map;
    }
}
```

</details>

### アプリのビルド

Java アプリをビルドします。
※ target/app.jar が作成されます。

```sh
$ mvn clean package
```

:::note info
ここまでの手順で、ローカル環境の **Ubuntu** に **JAR ファイル形式**のアプリをビルドすることができました。
:::

### コンテナイメージの作成

Dockerfile を作成します。

```sh
$ vim Dockerfile
```

ファイルの内容

```Dockerfile:Dockerfile
# from the base image of a jdk 11 container on Ubuntu 20.04.
FROM adoptopenjdk/openjdk11:x86_64-ubuntu-jdk-11.0.18_10-slim

# create a work dir.
WORKDIR /app

# copy a jvm app.
COPY target/*.jar app.jar

# open port 8080 for a jvm app.
EXPOSE 8080

# startup a jvm app.
ENTRYPOINT ["java","-jar","app.jar"]
```

<details>
<summary>説明を開きます。</summary>

:::note info
Dockerfile は、コンテナイメージをビルドするために必要な指示を記述したファイルです。

今回の例では以下のようなステップを実行します。

|命令|内容|
|:--|:--|
|FROM|ベースとなるコンテナイメージを指定します。この場合は、Ubuntu の slim バージョンのイメージを使用しています。|
|WORKDIR|コンテナー内で作業するディレクトリを指定します。ここでは、/app ディレクトリを作成しています。|
|COPY|ホストマシンからコンテナー内にファイルをコピーします。target/*.jar の部分は、target ディレクトリにあるすべての .jar ファイルを指定しています。また、app.jar という名前でコンテナー内にコピーされます。|
|EXPOSE|コンテナーが使用するポートを指定します。ここでは、8080 ポートを指定しています。|
|ENTRYPOINT|コンテナーが起動する際に実行されるコマンドを指定します。この場合は、java -jar app.jar が実行されます。|

このように Dockerfile を記述し、**docker build** コマンドでビルドすることで、コンテナイメージを作成できます。
:::

</details>

Docker デーモンを起動します。

```sh
$ sudo service docker start
 * Starting Docker: docker    [ OK ]
```

:::note warn
Docker 環境をお持ちでない場合は、以下の関連記事から Docker Engine のインストール手順をご確認いただけます。
:::

https://qiita.com/studio_meowtoon/items/3bceee25ac67cc73ebbe

コンテナイメージをビルドします。

```sh
$ docker build \
    --no-cache \
    --tag app-hello-spring-boot:latest .
```

<details>
<summary>説明を開きます。</summary>

:::note info
**docker build** コマンドは、現在のディレクトリにある Dockerfile を使用して、**app-hello-spring-boot という名前**のコンテナイメージをビルドします。ビルドされたイメージは、Docker ホスト上のローカルリポジトリに保存されます。**.** は、現在のディレクトリをビルドコンテキストとして使用することを指定します。
:::

</details>

コンテナイメージを確認します。

```sh
$ docker images | grep app-hello-spring-boot
REPOSITORY              TAG       IMAGE ID       CREATED          SIZE
app-hello-spring-boot   latest    39115028afa6   12 seconds ago   390MB
```

<details>
<summary>説明を開きます。</summary>

:::note info
**docker images** コマンドは、コンテナイメージのリストを表示します。**grep** コマンドは、指定された文字列を含む行をフィルタリングします。
:::

</details>

:::note info
ここまでの手順で、ローカル環境の **Docker** にアプリの**カスタムコンテナイメージ**をビルドすることができました。
:::

### コンテナーを起動

ローカルでコンテナーを起動します。
※ コンテナーを停止するときは ctrl + C を押します。

```sh
$ docker run --rm \
    --publish 8080:8080 \
    --name app-local \
    app-hello-spring-boot
```

<details>
<summary>説明を開きます。</summary>

:::note info
**docker run** コマンドで、**app-hello-spring-boot** という**コンテナイメージ**から、**app-local** という名前の**コンテナー**を作成し、ホストの 8080 ポートとコンテナーの 8080 ポートをマッピングします。つまり、ホストの 8080 ポートからアクセス可能なコンテナーが作成されます。
:::

</details>

:::note info
ここまでの手順で、ローカル環境の **Docker** でアプリの**カスタムコンテナー**を起動することができました。
:::

### コンテナーの動作確認

別ターミナルから curl コマンドで確認します。

```sh
$ curl -v http://localhost:8080/api/data -w '\n'
```

出力

```sh
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
> GET /api/data HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Fri, 09 Aug 2024 23:17:58 GMT
<
* Connection #0 to host localhost left intact
{"message":"Hello World!"}
```

:::note info
ここまでの手順で、ターミナルに **{"message":"Hello World!"}** と表示され、**JSON データ**を取得することができました。
:::

コンテナーの状態を確認してみます。

```sh
$ docker ps
CONTAINER ID   IMAGE                   COMMAND               CREATED         STATUS         PORTS                                       NAMES
377111ffea02   app-hello-spring-boot   "java -jar app.jar"   4 minutes ago   Up 3 minutes   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   app-local
```

<details>
<summary>説明を開きます。</summary>

:::note info
**docker ps** コマンドは、現在実行中のコンテナーの一覧を表示します。実行中のコンテナーの情報には、コンテナーID、コンテナー名、使用しているイメージ、ポートマッピング、作成された時間、状態などが含まれます。
:::

</details>

### コンテナーに接続

別ターミナルからコンテナーに接続します。

```sh
$ docker exec -it app-local /bin/bash
```

<details>
<summary>説明を開きます。</summary>

:::note info
**docker exec** コマンドで、コンテナー app-local に対して、対話的な bash シェルでコンテナー内部にログインします。**-i** と **-t** のオプションは、対話的な操作が可能な状態を作り出します。**/bin/bash** は、bash シェルを起動するためのコマンドです。
:::

</details>

コンテナーに接続後ディレクトリを確認します。
※ コンテナーから出るときは ctrl + D を押します。

```
# pwd
/app
# ls -lah
total 17M
drwxr-xr-x 1 root root 4.0K Aug  2 00:00 .
drwxr-xr-x 1 root root 4.0K Aug  2 09:07 ..
-rw-r--r-- 1 root root  17M Aug  1 23:46 app.jar
```

top コマンドで状況を確認します。

```sh
top - 10:49:52 up  1:32,  0 users,  load average: 0.01, 0.04, 0.01
Tasks:   3 total,   1 running,   2 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   7763.1 total,   6218.8 free,    668.9 used,    875.5 buff/cache
MiB Swap:   2048.0 total,   2048.0 free,      0.0 used.   6859.1 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    1 root      20   0 6504924 253188  20960 S   0.3   3.2   0:08.18 java
   52 root      20   0    7244   3816   3264 S   0.0   0.0   0:00.01 bash
   61 root      20   0    9072   3564   3060 R   0.0   0.0   0:00.05 top
```

コンテナーの情報を表示してみます。

```cs
# cat /etc/*-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.6 LTS"
NAME="Ubuntu"
VERSION="20.04.6 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.6 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
```

:::note warn
このコンテナーは Ubuntu をベースに作成されています。つまり、Ubuntu と同じように扱うことができます。
:::

## まとめ

WSL Ubuntu の Docker 環境で、Dockerfile からビルドした Spring Boot Web サービスのカスタムコンテナーを起動することができました。

:::note warn
クラウド開発においては、**Dockerfile の理解**は重要です。自動ビルドツールもありますが、手動で書く必要があるケースもあります。**Ubuntu** を使うと **Linux の知識**も身に付きます。最初は難しく感じるかもしれませんが、徐々に進めていけば自信を持って書けるようになります。
:::

どうでしたか？　WSL Ubuntu で、Spring Boot Web アプリケーションを Docker 環境でコンテナーとして手軽に起動できます。ぜひお試しください。今後も Java の開発環境などを紹介しますので、ぜひお楽しみにしてください。

## 推奨コンテンツ

https://qiita.com/studio_meowtoon/items/cbcfe1433885bae514db

https://qiita.com/studio_meowtoon/items/ef38ba2d5e913e343380

## 関連記事

ネイティブイメージビルドと比較してみましょう！

https://qiita.com/studio_meowtoon/items/fa77d2c1ba1a2c316ed0