💻

実務レベルのKotlin、Spring BootによるAPI環境構築とDockerコンテナ化

に公開

実務レベル環境構築ハンズオン

こんにちは、フリーランスエンジニアのたいち(@taichi_hack_we)です。
この記事ではKotlin / Spring Boot / PostgreSQLによるシンプルなバックエンドAPIを作成し、Dockerでコンテナ化するまでの手順をまとめました。

続編では、ここで作ったAPIをAWSにデプロイします。

Githubリポジトリは以下です。

https://github.com/taichi-web-engineer/aws-practice

Git、Githubの設定

aws-practiceリポジトリ作成

Githubでaws-practiceという名前でリポジトリを作成します。

Githubでaws-practiceのリポジトリ作成

リポジトリを作成したらgit cloneでローカルリポジトリを作成しましょう。

git clone git@github.com:taichi-web-engineer/aws-practice.git

以降、aws-practiceディレクトリをルートと呼びます。

不要ファイルをcommit対象から除外する.gitignore

グローバルなgitignore

macOSの一時ファイルなどを全リポジトリのcommit対象から除外するため、~/.config/git/ignoreを作成します。
ベースはGitHub公式macOS用テンプレートです。
さらに環境変数管理ツールdirenv(詳細は後で解説)の設定ファイル.envrcignoreに追加します。

~/.config/git/ignore
# General
.DS_Store
.AppleDouble
.LSOverride
Icon[]

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

.envrc

リポジトリ用.gitignore

ルート直下に.gitignore置きます。
.gitignoreの内容はChatGPT o3に以下のプロンプトで考えてもらいました。

kotlin、spring bootのwebアプリ用の.gitignoreのベストプラクティスを教えて

ChatGPTとの会話内容

o3の回答を調整した最終版が以下です。グローバルなgitignoreで設定しているもの、不要なものは削除しています。

https://github.com/taichi-web-engineer/aws-practice/blob/main/.gitignore

Kotlin、Spring Boot環境構築

Spring Initializrでプロジェクト作成

Spring Initializrにアクセスし、以下設定でZIPをダウンロードしてルートに展開します。

Spring Initializrの設定

Gradle - Kotlinを選ぶ理由は私が他のGroovyやMavenを使ったことがないためです。実務でもGradleがよく使われている印象です。

Spring Boot、Javaのバージョンは最新のLTS(安定)バージョンを選びます。

Project Metadataの概要は以下のとおりです。

  • Group:ドメインを逆にしたものを設定する。パッケージ名などで使われる
  • Artifact:プロジェクト自体のディレクトリ名などで使われる

GroupはAWSで取得するドメインをもとに設定します。私はaws-practice-taichi.comというドメインを取得するので、com.awsPracticeTaichiとしました。パッケージ名に「-」は使えないのでキャメルケースにしています。

DependenciesにはAPI、DB設定に必要なツールを追加しました。

依存ライブラリを最新のLTS(安定版)に更新

Kotlin、Spring Bootなど、各ライブラリのバージョンはapi/build.gradle.ktsで以下のように定義されています。

plugins {
	kotlin("jvm") version "1.9.25"
	kotlin("plugin.spring") version "1.9.25"
	id("org.springframework.boot") version "3.4.5"
	id("io.spring.dependency-management") version "1.1.7"
	kotlin("plugin.jpa") version "1.9.25"
}

group = "com.awsPracticeTaichi"
version = "0.0.1-SNAPSHOT"

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(21)
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-data-jpa")
	implementation("org.springframework.boot:spring-boot-starter-web")
	implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
	implementation("org.jetbrains.kotlin:kotlin-reflect")
	runtimeOnly("org.postgresql:postgresql")
	testImplementation("org.springframework.boot:spring-boot-starter-test")
	testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
	testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

kotlin {
	compilerOptions {
		freeCompilerArgs.addAll("-Xjsr305=strict")
	}
}

allOpen {
	annotation("jakarta.persistence.Entity")
	annotation("jakarta.persistence.MappedSuperclass")
	annotation("jakarta.persistence.Embeddable")
}

tasks.withType<Test> {
	useJUnitPlatform()
}

依存ライブラリのバージョンは最新のLTSを使いたいので、Gensparkのスーパーエージェントに以下のプロンプトで修正してもらいましょう。

以下のbuild.gradle.ktsの設定内容を最新のLTSバージョンに更新したいです

{build.gradle.ktsの全文をコピペ}

Gensparkとのやりとり

build.gradle.ktsの完成版は以下です。

https://github.com/taichi-web-engineer/aws-practice/blob/main/api/build.gradle.kts

detektという静的解析ツールを使いたいので、jvmのバージョン変更や関連ライブラリ追加をしています。(詳細は後ほど解説)

build.gradle.ktsを完成版と同じ内容に更新したら「すべてのGradle プロジェクトを同期」ボタンでbuild.gradle.ktsのライブラリやプラグインを反映できます。

すべてのGradle プロジェクトを同期

Gradle同期時にThe detekt plugin found some problemsという警告が出ますが、これはdetektの設定が未完了なため。あとで設定するので無視してOKです。

detektの警告

Docker & DB環境構築

Docker環境構築

Dockerを使うため、Docker DesktopOrbStackをインストールします。Appleシリコン製のMacユーザーはOrbStackを圧倒的におすすめします。OrbStackはDocker Desktopと同じ機能で動作が軽くて速いからです。詳細は以下の記事を参照してください。

https://qiita.com/shota0616/items/5b5b74d72272627e0f5a

docker helpコマンドが使えればDocker環境構築完了です。

docker help

Usage:  docker [OPTIONS] COMMAND

A self-sufficient runtime for containers

Common Commands:
  run         Create and run a new container from an image
  exec        Execute a command in a running container
  ps          List containers
  build       Build an image from a Dockerfile
  pull        Download an image from a registry
  ...

Docker Desktopを使っている方は以降のOrbStackをDocker Desktopに読み替えてください。

データベース環境構築

私のaws-practiceのGithubリポジトリからaws-practice/opsaws-practice/Makefileを取得して自身のaws-practiceの同じパスに配置してください。

aws-practice/ops/db-migrator/README.mdをもとにDB環境構築をします。知り合いのエンジニアが作成したGoのDBマイグレーションツールが使いやすいので活用しています。

https://github.com/taichi-web-engineer/aws-practice/blob/main/ops/db-migrator/README.md

README.mdに書いてあるとおりbrew install golang-migrategolang-migrateをインストールします。あとはops/db-migratorディレクトリで以下コマンドを順に実行すればDBにテーブルが作成されます。

docker compose up -d
go run main.go

テーブルを作成したら以下コマンドでテーブルにデータを入れます。

make restore DB=aws_practice

make restoreコマンドの実態はops/db-migrator/Makefileで定義している以下です。

# dumpファイルからseedデータを復元する。
# e.g.
# make restore DB=aws_practice
restore: .check-db
	docker compose exec -T db psql -U postgres -d $(DB) -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
	docker compose exec -T db psql -U postgres -d $(DB) < db/$(DB)/dump.sql

Makefileは複数のコマンドや変数を使ってコマンドを簡略化するコマンド集のようなものです。make restoreコマンドの実態は引数のチェックをしたあと2つのdocker compose execコマンドでDBにdump.sqlのseedデータを登録するコマンドというわけです。

データを入れたら、TablePlusなどのDBクライアントツールでaws_testテーブルのテストデータを確認できればOKです。

テストデータ

DBのDockerコンテナはマウントによるデータ永続化をしていません。Dockerコンテナを停止するとテストデータは削除されます。

永続化をしない理由はDBマイグレーションでDB環境をすぐ復元できるためです。復元のためのテストデータは以下のディレクトリで管理しています。

https://github.com/taichi-web-engineer/aws-practice/tree/main/ops/db-migrator/db/aws_practice

DBの立ち上げ & データ復元手順

DBを使うときは以下手順でDBを立ち上げてデータを復元できます。

  1. Docker Desktop or OrbStackの起動
  2. ops/db-migratorへcd
  3. docker compose up -d
  4. go run main.go
  5. make restore DB=aws_practice

direnvで環境変数の設定

DBのパスワードなど、Gitにコミットしたくないセキュアな情報は環境変数で管理しましょう。direnvというディレクトリごとに環境変数を設定できるツールを使います。

https://zenn.dev/masuda1112/articles/2024-11-29-direnv

direnvをインストールしたらルートからapiディレクトリに移動します。

cd api

環境変数を管理するファイルである.envrcを作成してエディタで開きます。私はVSCodeを使っています。

code .envrc

.envrcに以下のDB接続情報を追記して保存しましょう。ローカルのDocker環境のDBなので、接続情報はaws-practice/ops/db-migrator/compose.yamlで設定しているデフォルト値を使います。

export DB_HOST=localhost
export DB_NAME=aws_practice
export DB_PASSWORD=postgres
export DB_PORT=5432
export DB_USERNAME=postgres

.envrcを保存したらdirenv allow .を実行すればdirenvで環境変数を使う準備完了です。

Spring BootアプリのDB接続設定

Spring BootアプリのDB接続情報はapi/src/main/resources/application.propertiesで設定します。ただ、application.propertiesよりもyaml形式のapplication.ymlの方が階層構造で設定がわかりやすいのでリネームしてください。

リネーム後、application.ymlに以下の内容をコピペします。

https://github.com/taichi-web-engineer/aws-practice/blob/main/api/src/main/resources/application.yml

application.ymlの以下の部分で.envrcの環境変数を利用しています。

  datasource:
    url: jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME}
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    driver-class-name: org.postgresql.Driver

次に、IntelliJで.envrcの環境変数を使うために実行/デバッグ構成の環境変数の設定で.envrcを選択して適用しましょう。

IntelliJの環境変数設定

Spring Bootアプリを動かしてみる

ここまでの設定がうまくいっているかSpring Bootアプリを起動して確かめましょう。

Spring Bootアプリの起動確認

IntelliJの右上のApiApplicationの起動ボタンで起動できます。(DBを立ち上げていないと起動失敗します)

IntelliJのアプリ起動ボタン

コンソールに以下のような表示が出れば起動成功です。

2025-04-28T20:26:31.303+09:00  INFO 9834 --- [api] [           main] c.a.api.ApiApplicationKt                 : Started ApiApplicationKt in 0.953 seconds (process running for 1.137)

この状態でブラウザからlocalhost:8080へアクセスしても、ルートのエンドポイントが未実装なので404エラーになります。

404エラーページ

ルートのエンドポイントでDBのデータを返すようにする

以下4つのファイルを私のaws-practiceリポジトリと同じパスに配置してください。各ファイルのpackage com.awsPracticeTaichiの部分はSpring InitializrのProject MetadataのGroupで自身で設定した値に書き換えましょう。

処理の流れをざっと説明すると、

  1. ApiControllerでルートのエンドポイントへのアクセスを受ける
  2. ApiUsecaseAwsTestRepositoryを使ってDBのデータを取得してAwsTestのインスタンスに入れ、AwsTest.testTextを取得
  3. ApiControllerで取得したtestTestをListで返す

といった感じ。

APIでDBデータを取得して返す動作確認

アプリを再起動してlocalhost:8080へアクセスすると、APIがDBから取得したデータを返していることが確認できます。

APIのデータ取得成功画面

「プリティ プリント」という表示は私が使っているBraveブラウザが出しているもので、アプリとは無関係です。

静的解析ツールdetekt導入

Kotlin、Spring Boot環境ではdetektという静的解析ツール(LinterかつFormatter)をよく使います。
detektの使用例

detektを導入すると整っていないコードはこのようにハイライトされます。整っていないコードとは、

  • 不要なスペース、改行
  • importの順番
  • 未使用の変数
  • マジックナンバー

などです。整っていないコードを整形するフォーマット機能もあります。

初期設定

detektの設定は公式DocsのQuick Start with Gradleにそってやります。

といってもbuild.gradle.ktsのdetekt設定はすでに反映済なので、aws-practice/apiのディレクトリで公式Docsの手順通りgradlew detektGenerateConfigconfig/detekt/detekt.ymlを生成しましょう。

cd {path to aws-practice}/api

gradlew detektGenerateConfig

zsh: command not found: gradlewというエラーが出ます。公式Docsの通りにやるとエラーになる罠ですw

gradlewコマンドの実態はapi/gradlewにあるシェルスクリプトファイルです。なのでこれを実行するためには./gradlew detektGenerateConfigが正しいコマンドになります。コマンドが実行できるとapi/config/detekt/detekt.ymlが生成され、detekt設定は完了です。

IntelliJにdetektプラグインをインストール

ここまでの手順で、コマンドによるdetektの静的解析を実行できるようになりました。ですが、IntelliJでdetektのハイライトを出すためにはdetektプラグインが必要です。IntelliJの設定からdetektプラグインをインストールして適用しましょう。

IntelliJのdetektプラグイン

プラグインを適用するとdetektの設定ができるようになります。以下のように設定し、Configuration fileとしてconfig/detekt/detekt.ymlを追加して適用します。

detektプラグインの設定

detektの動作確認

適当なファイルで適当にスペースを入れ、以下のようにdetektのハイライトが出ればOKです。

detektの使用例

apiディレクトリで./gradlew detektを実行するとプロジェクトの全ファイルを対象にdetektの静的解析が行われます。ですが、今の状態で実行するとApiApplication.ktSpreadOperatorというdetektのチェックに引っかかります。

> Task :detekt FAILED
/Users/taichi1/Desktop/application/aws-practice/api/src/main/kotlin/com/awsPracticeTaichi/api/ApiApplication.kt:10:35: In most cases using a spread operator causes a full copy of the array to be created before calling a method. This may result in a performance penalty. [SpreadOperator]

エラーの概要は「スプレッド演算子(*)は内部的には配列のフルコピーをするのでパフォーマンスに悪影響かもしれないよ」といった感じです。

ApiApplication.kt
fun main(args: Array<String>) {
    runApplication<ApiApplication>(*args)
}

ただ、スプレッド演算子を使っている上記コードはKotlin、Spring Bootアプリの土台となるもので手を加えることはほぼありません。なのでSpreadOperatorのdetektチェックを無効にしましょう。detekt.ymlSpreadOperatoractive: falseにします。

detekt.yml
  SpreadOperator:
    active: false

ついでにdetekt.ymloutput-reports:の部分で

スキーマ検証: タイプに互換性がありません。
必須: array。 実際: null.

というwarningが出るので、空配列を設定してwarningを回避します。

detekt.yml
  exclude: []
  # - 'TxtOutputReport'
  # - 'XmlOutputReport'
  # - 'HtmlOutputReport'
  # - 'MdOutputReport'
  # - 'SarifOutputReport'

そして再度./gradlew detektを実行するとBUILD SUCCESSFULになるはずです。

./gradlew detekt --auto-correctを実行すると、プロジェクトの全ファイルをdetektがフォーマットします。適当なktファイルに不要なスペースを入れて保存し、./gradlew detekt --auto-correctを実行すれば動作確認ができます。

ファイル単位でのdetekt実行は右クリックで可能です。

ファイル単位のdetekt実行

detektのフォーマットはよく使うので、私はCtrl + Aのショートカットを割り当てています。

detektのフォーマットのショートカット設定

保存時に自動フォーマットが理想ですが、別途プラグインやツールが必要で面倒なため、私はショートカットを使っています。

detektの静的解析をcommit時に自動実行する

commit時にdetektを実行すれば、フォーマットの整っていないコードがcommitされることはありません。
aws-practice/.githooks/pre-commitに、commit時にdetektのチェックおよびフォーマットをかけるスクリプトがあるので、自身のaws-practiceへコピーしてください。

スクリプトの内容はdetekt公式Docsをもとにしています。
このスクリプトがcommit時に自動実行されるよう設定をしましょう。

ルートでgit config core.hooksPath .githooksを実行し、gitにスクリプトの場所を教えます。次にchmod +x .githooks/pre-commitでスクリプトの実行権限を付与して準備完了です。

適当なファイルに不要なスペースを入れてcommitすると、以下のようなエラーになってdetektのフォーマットが実行されます。

git commit -m "pre-commitテスト"
Running detekt check...

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':detekt'.
> Analysis failed with 1 weighted issues.

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.

BUILD FAILED in 436ms

> Task :detekt FAILED
/Users/taichi1/Desktop/application/aws-practice/api/src/main/kotlin/com/awsPracticeTaichi/api/usecase/ApiUsecase.kt:9:56: Unnecessary long whitespace [NoMultipleSpaces]

1 actionable task: 1 executed
***********************************************
                 detekt failed                 
 Please fix the above issues before committing 
***********************************************

アプリをDockerコンテナ化

Dockerfile作成

AWSのFargateでKotlin、Spring Bootアプリを動かすにはDockerコンテナ化する必要があります。GensparnkスーパーエージェントでDockerfileを作成しましょう。

「kotlin、springbootアプリケーションをDockerコンテナ化するDockerfileのベストプラクティスを教えてください」というプロンプトでひな型を作成します。

Gensparkとのやりとり

そしてAIとのやりとりを踏まえて修正、コメントを追記した完成版Dockerfileが以下です。aws-practice/api/Dockerfileとして配置してください。

https://github.com/taichi-web-engineer/aws-practice/blob/main/api/Dockerfile

またDockerイメージに不要なファイルが含まれないようaws-practice/api/.dockerignoreを作成します。.dockerignoreChatGPT o3に作成依頼をしました。

https://github.com/taichi-web-engineer/aws-practice/blob/main/api/.dockerignore

Dockerコンテナでアプリを動かしてみる

Dockerコンテナで正常にアプリが動くか動作確認をしましょう。まずaws-practice/apiディレクトへ移動します。

cd api

OrbStackを起動したあと、Dockerイメージをビルドします。

docker build -t aws-practice-api .

Dockerでアプリを起動する前にDBを立ち上げておかないと起動が失敗します。DB立ち上げ後、以下コマンドでDockerコンテナを起動しましょう。

docker run -p 8080:8080 \
  -e DB_HOST=host.docker.internal \
  -e DB_USERNAME=postgres \
  -e DB_PASSWORD=postgres \
aws-practice-api

host.docker.internalはホストマシンのIPアドレスに名前解決されます。IntelliJでのアプリ起動時はDB_HOST=localhostで環境変数を設定しました。

ですが今回アプリはDockerコンテナで動いているので、DB_HOST=localhostとするとアプリのDockerコンテナ自身を参照してDBにつなげません。なのでホストマシンで動いているDBに接続するため、DB_HOST=host.docker.internalとしてホストマシンに名前解決をしているわけです。

Dockerコンテナ起動後、ブラウザでlocalhost:8080へアクセスするとIntelliJのアプリ起動時と同じ画面が表示されます。

APIのデータ取得成功画面

Dockerコンテナのアプリ停止はCtrl + Cです。

Dockerイメージのセキュリティ対策

Trivyで脆弱性チェック

アプリのDockerイメージに脆弱性があると攻撃される危険があります。TrivyというOSSでイメージの脆弱性チェックをしましょう。

まずTrivyをインストールします。

brew install trivy

trivy image イメージ名のコマンドでイメージの脆弱性チェックができます。

trivy image aws-practice-api

以下の表示が出れば脆弱性は0件です。

Legend:
- '-': Not scanned
- '0': Clean (no security findings detected)

脆弱性チェックをGithub Actionsで定期実行する

定期的に手動で脆弱性チェックをするのはしんどいです。なのでGithub Actionsでtrivyを週1回0時に定期実行するようにしましょう。

今回もChatGPT o3にやり方を聞きました。

プロジェクトのDockerコンテナのTrivyによる脆弱性スキャンをGitHub Actionsで毎週日曜0時に実行したい

完成したGithub Actionsのymlファイルが以下です。これをaws-practice/.github/workflows/trivy-weekly-scan.ymlに配置してコミットします。

https://github.com/taichi-web-engineer/aws-practice/blob/main/.github/workflows/trivy-weekly-scan.yml

これで毎週日曜0時にSpring BootアプリのDockerイメージにtrivyによる脆弱性チェックが実行されます。

手動実行で動作確認もできます。自身のリポジトリのActionsタブ → Weekly Trivy Scan → Run workflowをクリックすれば手動実行可能です。

Github Actionsの脆弱性チェック手動実行

脆弱性チェックの結果はActionsタブのトップページに表示されます。緑のチェックは脆弱性なし、赤のバツは脆弱性ありです。

脆弱性チェック結果

またワークフローの詳細画面からチェック結果詳細をテキストファイルでダウンロードもできます。

脆弱性チェック結果のダウンロード

脆弱性ありのときはメールやslack通知を飛ばしたいですが、それは後ほど対応します。

次回:AWS環境構築

次回は今回作成したバックエンドAPIを使ったAWS環境構築します。お楽しみに!

https://zenn.dev/taichi_hack_we/articles/b2e94844c6b08d

Discussion