NestJS + Prisma + Docker + EC2 + GitHub Actionsで作るモダンなCI/CDパイプライン
1. はじめに
本記事では、NestJS、Prisma、Docker、Amazon EC2、GitHub Actions を使用して、CI/CD パイプラインを構築する方法を紹介します。初めてのデプロイということで問題アリアリだと思いますがどうぞ大目に見てください。
対象読者
- NestJS と Prisma を使用したバックエンド開発者
- Docker コンテナ化に興味がある開発者
- AWS EC2 でのデプロイを検討している方
- GitHub Actions を用いた CI/CD 構築を学びたい方
2. 環境設定
2.1 EC2 インスタンスの準備
まず、適切な EC2 インスタンスを選択します。本記事では、t3.micro(16GB)を使用します。以前は t2.micro(8GB)を使用していましたが、NestJS のビルド中にメモリ不足エラーが発生したため、アップグレードしました。
メモリを効率的に使用するために、ec2 コンテナに ssh で接続しスワップ領域を設定します:
sudo dd if=/dev/zero of=/swapfile bs=128M count=16
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# スワップ領域の確認
sudo swapon -s
# システム起動時の自動有効化設定
echo "/swapfile swap swap defaults 0 0" | sudo tee -a /etc/fstab
# メモリ状態の確認
free
2.2 Docker のインストール
EC2 インスタンスに Docker をインストールします。Ubuntu の場合、公式ドキュメントに従ってインストールを行います。
3. NestJS アプリケーションの Dockerfile 作成
3.1 基本的な Dockerfile
NestJS アプリケーションの Dockerfile を作成します。以下は基本的な構成です:
FROM node:18-alpine AS build
WORKDIR /usr/src/app
COPY package.json yarn.lock ./
RUN yarn install --production --frozen-lockfile
COPY . .
RUN yarn cache clean
RUN yarn build
FROM node:18-alpine AS production
COPY /usr/src/app/node_modules ./node_modules
COPY /usr/src/app/dist ./dist
CMD [ "node", "dist/src/main.js" ]
参考資料
3.2 Prisma を使用する際の注意点
Prisma を使用する場合、prisma generate
の結果を適切に扱う必要があります。以下の変更を行います:
-
schema.prisma
ファイルを修正し、生成されるクライアントの出力先を変更します:
generator client {
provider = "prisma-client-js"
output = "./generated/client"
binaryTargets = ["native", "debian-openssl-3.0.x", "linux-musl-openssl-3.0.x"]
}
- PrismaClient のインポート先を変更します:
import { PrismaClient } from 'prisma/generated/client';
3.3 最終的な Dockerfile
Prisma の設定を反映した最終的な Dockerfile は以下のようになります:
FROM node:18-alpine AS build
WORKDIR /usr/src/app
COPY package.json yarn.lock ./
RUN yarn install --production --frozen-lockfile
COPY . .
RUN yarn cache clean
ARG AWS_ACCESS_KEY_ID
ARG AWS_SECRET_ACCESS_KEY
ARG AWS_REGION
ARG FRONTEND_BASE_URL
ARG COGNITO_CLIENT_ID
ARG COGNITO_USER_POOL_ID
ARG DATABASE_URL
ARG OPENAI_API_KEY
RUN yarn build
FROM node:18-alpine AS production
COPY /usr/src/app/node_modules ./node_modules
COPY /usr/src/app/dist ./dist
COPY prisma ./dist/prisma/
CMD [ "node", "dist/src/main.js" ]
4. GitHub Actions ワークフローの作成
4.1 ワークフローの基本構造
.github/workflows/ci-cd.yml
ファイルを作成し、以下の内容を記述します:
name: CI/CD Pipeline
on:
push:
branches:
- main
jobs:
build-backend:
name: Build and Deploy Backend
runs-on: ubuntu-latest
steps:
# バックエンドのビルドとデプロイ手順
build-frontend:
name: Build and Deploy Frontend
runs-on: ubuntu-latest
needs: build-backend
steps:
# フロントエンドのビルドとデプロイ手順
4.2 バックエンドのビルドとデプロイ
バックエンドのビルドとデプロイには、以下の手順を含めます:
- コードのチェックアウト
- Docker Hub へのログイン
- Docker イメージのビルドとプッシュ
- EC2 インスタンスへの SSH 接続とデプロイ
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login Docker Hub
uses: docker/login-action@v3
with:
username: ${{secrets.DOCKER_HUB_USERNAME}}
password: ${{secrets.DOCKER_HUB_ACCESS_TOKEN}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./backend
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/myservice-backend:latest
build-args: |
AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION=${{ secrets.AWS_REGION }}
FRONTEND_BASE_URL=${{ secrets.FRONTEND_BASE_URL }}
COGNITO_CLIENT_ID=${{ secrets.COGNITO_CLIENT_ID }}
COGNITO_USER_POOL_ID=${{ secrets.COGNITO_USER_POOL_ID }}
DATABASE_URL=${{ secrets.DATABASE_URL }}
OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_KEY }}
script: |
# デプロイスクリプト
4.3 (おまけ)フロントエンドのビルドとデプロイ(S3 + CloudFront)
フロントエンドのビルドとデプロイには、以下の手順を含めます:
- Node.js のセットアップ
- 依存関係のインストール
- フロントエンドのビルド
- AWS クレデンシャルの設定
- S3 へのデプロイ
- CloudFront の無効化
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Dependencies
run: |
cd frontend
npm install
- name: Build Frontend
env:
FRONTEND_BASE_URL: ${{ secrets.FRONTEND_BASE_URL }}
BACKEND_BASE_URL: ${{ secrets.BACKEND_BASE_URL }}
run: |
cd frontend
npm run build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Deploy to S3
run: |
aws s3 sync frontend/dist s3://myservice-app --delete
- name: Invalidate CloudFront
run: |
aws cloudfront create-invalidation --distribution-id <distribution-id> --paths "/*"
5. CI/CD パイプラインの解説
5.1 バックエンドのデプロイフロー
- コードが main ブランチにプッシュされると、ワークフローが開始されます。
- Docker イメージがビルドされ、Docker Hub にプッシュされます。
- EC2 インスタンスに接続し、最新の Docker イメージをプルして実行します。
5.2 フロントエンドのデプロイフロー
- バックエンドのデプロイが完了後、フロントエンドのビルドが開始されます。
- ビルドされたファイルは S3 バケットに同期されます。
- CloudFront のキャッシュが無効化され、最新のコンテンツが配信されます。
5.3 環境変数の取り扱い
機密情報は GitHub Secrets に保存し、ワークフロー内で環境変数として使用します。これにより、セキュアな方法で必要な情報を CI/CD プロセスに組み込むことができます。
6. トラブルシューティング
よくあるエラーとその解決方法:
- Prisma 関連のエラー:
prisma generate
の実行結果が正しく含まれていることを確認してください。 - メモリ不足エラー:EC2 インスタンスのスペックアップやスワップ領域の設定を検討してください。
- Docker 関連のエラー:Dockerfile の構成を見直し、必要なファイルが正しくコピーされているか確認してください。
7. まとめ
本記事では、NestJS、Prisma、Docker、EC2、GitHub Actions を組み合わせた CI/CD パイプラインの構築方法を紹介しました。
初めてのデプロイということであまり詳しく書けず色々とツッコミどころが多いと思います。もし改善点であったり問題点がありましたらお教えください。ありがとうございました。
Discussion