GitHub Actionsでnest.jsの軽量な運用イメージを作成しデプロイ

GitHub Actionsでnest.jsの軽量な運用イメージを作成しデプロイ

ども!今回は、nest.jsのDocker環境プロダクションビルドをGitHub Actions寄りに変更しました。CI上でnpmのキャッシュを活用して断続的なビルドを効率的に行えるように調整しています。アプリケーションのビルドをCI上に切り出すことで、テストの結果などもGitHub上で管理することができます。

はじまり

ども!すんごく珍しく二日連続で出社して、一年ぶりに同期と顔を合わせてびっくりされた龍ちゃんです。入社当時からリモートワークだと、偶然以外で誰かと会おうとすると気合が必要になりますね。同じ部署だとイベントで会いますけど、別部署の同期とはなかなかですね..

さて!今回はnest.jsの運用環境周りにまつわる話です。前回は、nest.jsのプロダクション環境用Dockerfileを作成して、Azure Web Appsにデプロイしました。前回のファイルでも、デプロイ自体は問題ありません。ですが、以下の問題点があります。

  • アプリケーションのビルドが内部に閉じている
  • npmで実行するテストコードの結果をGitHub Actionsで表示できない
  • 不要なnode_modulesもコンテナの中に含まれてしまう

GitHub Actionsでテストを回しにくい環境は、あまり理想的ではありませんよね。極力効率化するのがエンジニアの使命であるということにしましょう。

それでは本題になります。

GitHub Actionsでnest.jsの軽量プロダクション環境

今回用意するのは、Dockerfile.prodとワークフロー用のymlになります。ディレクトリ構造としては以下になります。アプリケーションとしては、すべてbackendに収まっている形になります。

.
├── Dockerfile
├── Dockerfile.prod               # 運用環境用Dockerfile
├── docker-compose.yml            
└── backend/                      # Nest.jsのプロジェクト

Dockerfile.prod

ARG NODE_VER
FROM node:20.10.0

USER node
WORKDIR /home/node/app

COPY --chown=node:node ./backend/node_modules/ ./node_modules/
COPY --chown=node:node ./backend/dist ./dist/

EXPOSE 3000

ENTRYPOINT ["node", "dist/main.js"]

こちらのDockerfileでは、node_modulesとビルド済みのアプリdistをコピーしています。

GitHub Actionsワークフロー

デプロイのために、トークンを発行して環境”production”にTOKEN_CLASSICとして設定しています。GitHub Actionsのフローの流れを説明します。

GitHubワークフローの流れ

3つの段階に分けています。今回意識した部分としては、以下になります。

  • actions/setup-nodeを用いてキャッシュを活用する
  • 処理の目的ごとに分ける。アプリケーションのビルドを切り出す
  • アプリケーションのビルドは、actions/cacheを使用して共通化する
  • CIでテストの実行場所を確保する

実際のワークフローが以下になります。

#
name: Development CI

# Configures this workflow to run every time a change is pushed to the branch called `release`.
on:
  push:
    branches: ["main"]
env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}
  NODE_VERSION: "20.x"

jobs:
  build-app:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{env.NODE_VERSION}}
          cache: "npm"
          cache-dependency-path: "**/package-lock.json"

      - name: npm install node_modules
        run: npm install
        working-directory: backend

			# ここでCI上のテスト実行場所を確保する

      - name: npm build
        run: npm run build
        working-directory: backend

      - name: Upload Cache app
        uses: actions/cache@v4
        with:
          path: backend/dist/
          key: ${{ runner.os }}-app-${{ github.sha }}

  build-and-push-image:
    runs-on: ubuntu-latest
    needs: "build-app"
    environment: "production"
    outputs:
      image-name: ${{steps.build-name.outputs.container}}

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Download Cache app
        uses: actions/cache@v4
        with:
          path: backend/dist/
          key: ${{ runner.os }}-app-${{ github.sha }}

      - uses: actions/setup-node@v4
        with:
          node-version: ${{env.NODE_VERSION}}
          cache: "npm"
          cache-dependency-path: "**/package-lock.json"

      - name: npm install production
        run: npm install --production
        working-directory: backend

      - name: Log in to the Container registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.TOKEN_CLASSIC }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: ${{ github.sha }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./Dockerfile.prod
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

      - name: build container name
        id: build-name
        run: |
          echo "container=${{steps.meta.outputs.tags}}" >> $GITHUB_OUTPUT

  deploy-to-webapps:
    runs-on: ubuntu-latest
    needs: [build-and-push-image]
    environment: "production"
    steps:
      - uses: azure/webapps-deploy@v2
        with:
          app-name: "line-liff-backend"
          publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
          images: "${{needs.build-and-push-image.outputs.image-name}}"

アプリケーションとイメージのビルド時の二度「npm install」しているのは、アプリケーションのビルドで作成したnode_modulesをキャッシュで共通化すると、不要なファイルが共有される可能性が多くあるのと、actions/setup-nodeの機能でキャッシュが効果的で、npm install —productionで実行に必要なnode_modulesを作成するという目的があります。

WEB Appsのデプロイ部分に関しては、以下の記事を参考にしていただければ幸いです。トークンの発行部分なども併せて解説しています。

Nest.jsをGitHubコンテナレジストリー経由でWeb Appsデプロイ

おわり

ども!Azure Web Appsにデプロイまでを記事にしたので、もう書くことないやと思っていたら上司からの声が降ってきて自分の考慮の甘さではずかしくなりました。

今までは、フロントのE2Eテストとコンポーネントの画面見ながらのテストばかりやっていたので、コードでテストを書くという経験がほぼないです。これからは、テストも書くという経験も発信できればなと思いますわ!

ではでは!

Twitterもよろしく~

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です