はじめに
Spring Boot アプリを開発して「いざデプロイ!」となったとき、こんな疑問が湧いた。
- Maven でビルドするって具体的に何をしてるの?
- Dockerfile はどう書けばいい?
- GitHub に push したら自動でデプロイされるようにしたい
今回は上記の疑問をまとめて解消する形で、GitHub Actions + Docker を使った Spring Boot の CI/CD パイプラインを実際に構築した手順を記録する。
全体の流れ
前提・環境
| 項目 | バージョン |
|---|---|
| Java | 21 |
| Spring Boot | 3.x |
| ビルドツール | Maven 3.9.x |
| コンテナ | Docker |
| CI/CD | GitHub Actions |
| デプロイ先 | Render(または SSH 経由の VPS) |
Gradle を使っている場合も、ビルドコマンド部分を ./gradlew bootJar に変えるだけで同じ構成が使える。
1. Maven によるビルドの仕組み
Maven とは何をしているのか?
Spring Boot プロジェクトでは mvn package コマンドを使うことが多い。
内部では以下のフェーズが順番に実行される。
validate → compile → test → package
| フェーズ | 内容 |
|---|---|
validate |
pom.xml の構文チェック |
compile |
.java → .class へコンパイル |
test |
JUnit テストを実行 |
package |
target/ 配下に実行可能な .jar を生成 |
テストが失敗するとビルドも止まる。CI 環境でテストをスキップしたい場合は -DskipTests を付けるが、基本的には通るように直すべき。
pom.xml の最小構成(Spring Boot 3.x)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Spring Boot 親POMを継承することで依存管理が楽になる -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>myapp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<!-- Web (REST API) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- テスト -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- このプラグインが「実行可能 fat JAR」を作ってくれる -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
spring-boot-maven-plugin がポイント。これがあることで、依存ライブラリをすべて含んだ fat JAR(実行可能 JAR) が target/myapp-0.0.1-SNAPSHOT.jar に生成される。
2. Dockerfile を書く
生成した .jar を Docker コンテナに乗せる。
マルチステージビルドを使うことでイメージサイズを削減できる。
# ---- Stage 1: ビルド ----
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /app
# 依存解決だけ先にキャッシュさせる(変更が少ない層を先に積む)
COPY pom.xml .
COPY .mvn/ .mvn/
COPY mvnw .
RUN chmod +x mvnw && ./mvnw dependency:go-offline -B
# ソースをコピーしてビルド
COPY src ./src
RUN ./mvnw package -DskipTests -B
# ---- Stage 2: 実行 ----
FROM eclipse-temurin:21-jre
WORKDIR /app
# ビルドステージから .jar だけコピー
COPY --from=builder /app/target/*.jar app.jar
# ポート公開(Spring Boot デフォルト)
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
ポイント解説
| 記述 | 理由 |
|---|---|
eclipse-temurin:21-jre |
JDK より軽量な JRE ベースイメージを使う |
| マルチステージビルド | ビルドツールが含まれない分、本番イメージが軽くなる |
dependency:go-offline |
pom.xml が変わらなければ依存ダウンロードをキャッシュ活用 |
-DskipTests |
CI でのビルド時間短縮(テストは別ステップで実施済み) |
3. GitHub Actions ワークフローを作る
.github/workflows/deploy.yml を作成する。
name: CI/CD Pipeline
# main ブランチへの push 時に起動
on:
push:
branches:
- main
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }} # owner/repo 形式
jobs:
build-and-deploy:
runs-on: ubuntu-latest
# GHCR へのwrite権限を付与
permissions:
contents: read
packages: write
steps:
# ① コードをチェックアウト
- name: Checkout code
uses: actions/checkout@v4
# ② Java セットアップ
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: maven # ~/.m2 をキャッシュして高速化
# ③ Maven でテスト&ビルド
- name: Build with Maven
run: ./mvnw package -B
# ④ Docker Buildx セットアップ(マルチプラットフォーム対応)
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# ⑤ GHCR にログイン
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} # 自動付与されるトークン
# ⑥ イメージをビルド&プッシュ
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ⑦ デプロイ(Render の場合)
- name: Deploy to Render
if: success()
run: |
curl -X POST "${{ secrets.RENDER_DEPLOY_HOOK_URL }}"
secrets の設定方法
GitHub リポジトリの Settings → Secrets and variables → Actions から以下を登録する。
| Secret 名 | 内容 |
|---|---|
RENDER_DEPLOY_HOOK_URL |
Render の Deploy Hook URL |
GITHUB_TOKEN は Actions が自動で発行するため、手動登録不要。
4. デプロイ先の設定
パターン A: Render を使う(お手軽)
- Render でアカウント作成
- New Web Service → Deploy an existing image
- イメージ URL に
ghcr.io/<owner>/<repo>:latestを指定 - Deploy Hook URL をコピーして GitHub Secrets に登録
GHCR の認証設定(Render 側)
Render の Environment → Registry Credentials で以下を設定する。
| 項目 | 値 |
|---|---|
| Registry URL | ghcr.io |
| Username | GitHub ユーザー名 |
| Password | GitHub の Personal Access Token(read:packages 権限) |
パターン B: VPS に SSH デプロイ
SSH 鍵を Secrets に登録し、GitHub Actions からサーバーに入って docker pull & run する。
# ⑦ SSH でサーバーにデプロイ(VPS の場合)
- name: Deploy to VPS
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
echo ${{ secrets.GITHUB_TOKEN }} | \
docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker pull ghcr.io/${{ github.repository }}:latest
docker stop myapp || true
docker rm myapp || true
docker run -d \
--name myapp \
-p 8080:8080 \
--env-file /home/deploy/.env \
ghcr.io/${{ github.repository }}:latest
| Secret 名 | 内容 |
|---|---|
SERVER_HOST |
サーバーの IP または ドメイン |
SERVER_USER |
SSH ユーザー名 |
SERVER_SSH_KEY |
SSH 秘密鍵(~/.ssh/id_ed25519 の内容) |
秘密鍵はそのまま Secrets に貼り付ける(-----BEGIN... から -----END... まで含めて)。
サーバー側の ~/.ssh/authorized_keys に対応する公開鍵を登録しておく。
5. ディレクトリ構成まとめ
myapp/
├── .github/
│ └── workflows/
│ └── deploy.yml # ← CI/CD パイプライン定義
├── src/
│ └── main/java/com/example/
│ └── MyAppApplication.java
├── Dockerfile # ← マルチステージビルド
├── pom.xml # ← Maven 設定
└── mvnw # ← Maven Wrapper(環境差異をなくす)
6. よくあるトラブルと対処
Permission denied で GHCR に push できない
permissions:
packages: write # ← これが抜けていることが多い
Render でイメージが取得できない
GHCR のパッケージが private になっている場合、Render 側に認証設定が必要(前述の Registry Credentials 参照)。
./mvnw: not found エラー
mvnw ファイルをコミットし忘れているケース。または実行権限がないケース。
git add mvnw .mvn/
chmod +x mvnw
Docker イメージが大きい
マルチステージビルドで jre ベースにするだけでも 150MB 以上削減できる。
さらに軽量にしたい場合は eclipse-temurin:21-jre-alpine を検討。
まとめ
| ステップ | やること |
|---|---|
| Maven |
pom.xml に spring-boot-maven-plugin を追加、./mvnw package で fat JAR 生成 |
| Dockerfile | マルチステージビルドで .jar だけを実行イメージに乗せる |
| GitHub Actions | push をトリガーに Maven ビルド → Docker push → デプロイを自動化 |
| デプロイ先 | Render(お手軽)or VPS(SSH + docker run) |
一度パイプラインを作ると、あとは git push するだけでデプロイまで完結するのが快感だった。
次のステップとして、PostgreSQL の接続設定や docker compose を使った本番環境整備に取り組む予定。