0
0

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 アプリを開発して「いざデプロイ!」となったとき、こんな疑問が湧いた。

  • 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 を使う(お手軽)

  1. Render でアカウント作成
  2. New Web ServiceDeploy an existing image
  3. イメージ URL に ghcr.io/<owner>/<repo>:latest を指定
  4. 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.xmlspring-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 を使った本番環境整備に取り組む予定。


参考

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?