Next.jsを Docker × Github actions × ECR × App Runner使ってデプロイする

こんにちは。ゆせです。
今回は、備忘録として、Next.jsを Docker × GithubActions × ECR × App Runnerを使ってデプロイする方法を簡単にまとめておこうと思います。

## 前提
少し長めなので、ロジックや細かい解説は省き、基本的には簡単な意思決定背景、それぞれのファイルに何を書くかや、詰まったところをちょこちょこ、簡単な解説ができればと思います。誰かのお役に立てれば嬉しいです。

## なぜこれらでデプロイしようと思ったか
単純にハッカソン型インターンでデプロイすることになったからです。笑 個人的にそこまで難しいことはしなかったイメージです。App Runnerの他にもEC2等があげられますが、工数的にApp Runnerの方が軽いと判断したため、App Runnerを採用しました。

## ECRを採用した理由は？
App Runnerでデプロイする際に、方法は二種類あります。以下の画像をご覧ください。
![スクリーンショット 2023-09-29 19.25.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3236084/e37a3e71-2e58-8a87-0e35-bc072bb45f38.png)

画像の通り、ECRとは別で、githubと連携してデプロイすることができるそうです。ECRよりおそらくgithub連携のほうが簡単にデプロイが終わると思いますが、今回は、githubのorganizationの権限上、連携するための許可がハッカソンの期間中に出ないと判断されたため、権限が関係ないECRでデプロイしました。経験として私は良かったですが、「とりあえず早くデプロイしたい。。」みたいな方はECRではなくgithub連携がいいのかもしれません。。ただ、触れるものも多いので、何かと勉強になります。

ちなみに上の画像の引用元は、以下のリンクなのですが、App Runnerについて粒度の高い内容となっているので、是非見てみることを推奨します！
https://qiita.com/yoshii0110/items/ec209712cafa7547c680

## 具体的にどんな流れ？
全体の流れとしては、
Github ActionsでDockerコンテナをビルド
↓
ビルドしたものをECRに保存
↓
保存したものをApp runnerに自動デプロイ

厳密にはあっているか少し自信ないのですが、大まか流れはこんな感じです。

## コードを書いていく
本題です。まずは、デプロイするnext.jsの環境にディレクトリを移動してください。
今回は、デプロイをいつしてもいい状態という想定で話を進めていきます。
デプロイだけしてみたいみたいな人は、以下のコマンドを叩いて、セットアップしちゃいましょう！

```console
$ npx create-next-app@latest
```

## Docker（本番環境）
まずDockerをまだ一切自分のPCで使ったことがない人は、その設定をしないといけないので、「Docker インストール」などで調べてみてください！
以下のコードを記載します(念のためブランチ分けてもいいかもです/私はわけました)

```Docker:Dockerfile
# syntax=docker/dockerfile:1

# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/engine/reference/builder/

ARG NODE_VERSION=18.16.0

################################################################################
# Use node image for base image for all stages.
FROM node:${NODE_VERSION}-alpine as base

# Set working directory for all build stages.
WORKDIR /usr/src/app


################################################################################
# Create a stage for installing production dependecies.
FROM base as deps

# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /root/.yarn to speed up subsequent builds.
# Leverage bind mounts to package.json and yarn.lock to avoid having to copy them
# into this layer.
RUN --mount=type=bind,source=package.json,target=package.json \
    --mount=type=bind,source=package-lock.json,target=package-lock.json \
    --mount=type=cache,target=/root/.npm \
    npm ci --production --frozen-lockfile

################################################################################
# Create a stage for building the application.
FROM deps as build

# Copy the rest of the source files into the image.
COPY . .
# Run the build script.
RUN npm run build

################################################################################
# Create a new stage to run the application with minimal runtime dependencies
# where the necessary files are copied from the build stage.
FROM base as final

# Use production node environment by default.
ENV NODE_ENV production

# Run the application as a non-root user.
USER node

# Copy package.json so that package manager commands can be used.
COPY package.json .

# Copy the production dependencies from the deps stage and also
# the built application from the build stage into the image.
COPY --from=deps /usr/src/app/node_modules ./node_modules
COPY --from=build /usr/src/app/.next ./.next

# Run the application.
CMD npm start

EXPOSE 3000

```
```docker-compose.yml
version: "3"

services:
  next-app:
    container_name: next-app-prod
    image: next-app-prod
    build:
      context: .
      dockerfile: Dockerfile
      args:
        ENV_VARIABLE: ${ENV_VARIABLE}
        NEXT_PUBLIC_ENV_VARIABLE: ${NEXT_PUBLIC_ENV_VARIABLE}
    restart: always
    ports:
      - 8000:3000

```
保存した後、本番用にアプリケーションをビルドし、本番サーバーも起動しましょう。

```console
$ npm run build
$ npm run start
```

その後、コンテナを起動しておきます。
```console
$ docker compose up
```
無事全て成功したら次に行きましょう

## GithubActionsのワークフロー
`deploy.yml`というファイルを作成して、隠しファイルとして書いていきます。プロジェクト内に新しく、 ```.github/workflows/deploy.yml```を作成しましょう。
```
project
├─ .github
   └─workflows
     └─deploy.yml
├─ src
```

```deploy.yml
name: Build and Push

on:
  push:
    branches:
      - main
      - (もしブランチを切ってコードを書いている場合はそのブランチ名も書きましょう。pushした時にbuildしてほしいブランチを指定できる)
jobs:
  build-and-push:
    runs-on: ubuntu-latest
    timeout-minutes: 300

    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build, tag, and push image to Amazon ECR
        env:
          ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPO_NAME }}
        run: |
          docker build -t $ECR_REPOSITORY:latest .
          docker push $ECR_REPOSITORY:latest

```
上記には、`v4`や`v1`がありますが、これは以下のgithubを参考にしました。（が常にアップデートされて本記事でもエラーが起きる可能性があるので注意してください）(私もいろんな記事を参考にしましたが、`v3`で実装しててエラーが起きました)

https://github.com/aws-actions/configure-aws-credentials

## AWS周りを設定をする
AWSの設定をしましょう。以下二つを行う必要があります。
- AWS IAM ユーザーの作成
- Amazon ECRのリポジトリの作成

### AWS IAMユーザーの作成
GitHub ActionからAmazon ECRへアクセスする為の､IAMユーザーを発行します｡サインアップで、IAMユーザーで登録、ログインしてください。
ここで発行されるアクセスキー､シークレットアクセスキーは後ほど使用するのでメモして置きます｡
※シークレットアクセスキーは、一度しか見れないので、必ず表示されたらメモしましょう。

![スクリーンショット 2023-10-06 11.32.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3236084/a8be95ab-0586-6183-c3de-90abda5f829b.png)
IAMを選択、おそらく画面真ん中にIAMリソースが表示されており、ユーザーを選択。すると以下の画像のような画面が出ますので、許可を追加を押して、ポリシーを直接アタッチします。
※ここで許可ポリシーを設定する画面が出てくると思いますが、何にするかは、用途によって変わってきます(今回のように、ただCI構築して,デプロイしてで終わりなのか、それとも運用していくのか)。そこは、開発チームや状況によって変わると思うので、確認、調査してみてください。
![スクリーンショット 2023-10-06 11.33.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3236084/f1dfa9b9-baa0-5f12-ddbc-db119d5a9f96.png)


### Amazon ECRのリポジトリの作成
![スクリーンショット 2023-10-06 10.50.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3236084/b84d222c-38db-8979-0984-801a5fd6c81e.png)
この画面からElastic Container Registryを選択し、リポジトリを作成ボタンがあると思うので、そこからリポジトリを作成しましょう。リポジトリ名は任意です。

## GitHubリポジトリにSecretsを設定
あと少しです。次に、`AWS_ACCESS_KEY_ID`と`AWS_SECRET_ACCESS_KEY`, `AWS_ECR_REPO_NAME`をgithubリポジトリ内で設定します。
GitHubリポジトリを開き､ Settings → Secrets and variebles からActionsを設定します｡ここで設定した値は､GitHub Action ワークフロー内で ${{ secrets.AWS_ACCESS_KEY_ID }} という記法で取り出しができます｡
3つを設定してください｡(以下画像参考にしてください)
![スクリーンショット 2023-10-05 13.56.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3236084/ed8dcb5c-e393-479f-3d6a-c3b218d8a311.png)

## Pushしてみる
それでは、Dockerファイルや、deploy.ymlファイルをpushしてみましょう。
pushして、githubの該当リポジトリ→Actionsを見てみると、github actionsがおそらく回っていることでしょう。以下になれば成功です。(commit名おかしいけど。。笑)
![スクリーンショット 2023-10-06 11.47.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3236084/eb003c08-8595-6a9c-dead-422bd7be49d9.png)

## あとはapp runner設定するだけ！
![スクリーンショット 2023-10-06 11.48.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3236084/ec26b38f-4a59-f42b-01c0-a66a6ec9edd7.png)
![スクリーンショット 2023-10-06 11.49.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3236084/df13ae94-6d84-44dd-a33e-c008011cb8f7.png)
コンテナイメージのURIは「参照」を押してみると、もう選択肢として、ECRのURIが自動で出でくると思います。

諸々設定画面が出てくると思いますが、ほとんどデフォルト設定でokです！一応怖いからちゃんと設定教えてくれよ泣って方は調べてみてください🙇‍♂️

あとは画面が勝手に切り替わってくれます。デプロイしたURLも発行されます。
以上で終わりですお疲れ様でした。

## 感想
どうだったでしょうか。AWS,大変ですね。ただ、設定としては比較的優しめな気もします。コンテナ化されたアプリケーションの実行環境を提供してくれる、かつ、それだけでなく、CI/CDの準備、ロードバランサーによるオートスケール、カスタムドメイン周りなども管理してくれています。ここら辺は正直私もわからないことも多いのですが、Docker, CICD, ECRなど、触るものが多かったので、何かと勉強になりました。
デプロイの手段はさまざまで、どんな言語に対応してるか、デプロイ方法は簡単か、値段が安いか、パフォーマンスはいい感じか、などの軸でそれぞれが決めてOKって感じです、と、私のメンターさんから言われた通りだと思います。ただ、冒頭に申し上げた通り、結局、react/nextjsは結局Vercelが一番楽ですね。。笑
本記事が誰かのお役に少しでも立てたら嬉しいです。

### おまけ
私のメンターさんに「ECR連携と、github連携の何が違うんですか？」と聞いてみた時のレスです。参考にしてみてください！
> ECR連携
・コンテナイメージだけを入れ替えてすぐリリースすることができる（例えば、障害時に一つ前のコンテナイメージにすぐ切り替えられる）
・環境構築として、コンテナイメージを手元で作れるようにしてECRにアップロードする手間がある
github連携
・レポジトリ指定して構築コマンド・起動コマンドの2つを指定するだけなのですごい簡単
・最新の GitHub を参照してるのみなのでコンテナイメージを入れ替えられないデメリットはある
その他
・cloud run(GCP) に比べるとちょっと劣っている

以下は、app runnerについて、ネガティブに書かれた記事です。周りのつよつよや現役エンジニアに聞いたところ、賛否両論(例: app runner全然いいでしょ派 or EC2でいいじゃん派)あったため、ほんとに手段や目的次第なんだなと改めて実感しました。(決して、以下の記事を悪く言っているわけではありません。私自身、実際すこし納得した部分も多かったです笑)

https://koudenpa.hatenablog.com/entry/2022/05/04/183326