🚀

【Terraform】AmplifyでNext.jsのSSRアプリをデプロイ

2024/02/18に公開

やりたいこと

Next.jsを使ってwebアプリのフロントエンドを作ったので、Terraformを使ってAmplifyでデプロイしてみます。

なぜAmplifyなのか

SSRで構築したかったことが第一にあります。
Amplify以外だとVercel, Netlifyあたりも選択肢になりますが、

  • 会社でローンチしているサービスがほぼAWSで構築されていること
  • それらがTerraformで管理されていること

により、業務に知見を活かせそうなAmplifyを選択しました。
なおSSRについては以下の記事が大変参考になります。
https://zenn.dev/luvmini511/articles/1523113e0dec58

環境

nodeバージョン: 20.11.0
nextバージョン:14.0.4
terraformバージョン:1.7.0

Terraformを実装

まずmain.tfを作ります。

main.tf
provider "aws" {
  region = "ap-northeast-1"

  default_tags {
    tags = {
      "Service"       = "myapp"
      "Description"   = "Managed by Terraform"
      "CreatedByRole" = "terraform"
    }
  }
}

terraform {
  required_version = ">= 1.7.0"

  backend "s3" {
    bucket = "terraform-2024"
    key    = "myapp/service/production/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

awsのアカウントIDを自動で取得したいのでdata.tfに記載します。

data.tf
data "aws_caller_identity" "current" {}

続いてIAMの設定を行います。

iam.tf
resource "aws_iam_role" "myapp" {
  name = "myapp"

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Action = "sts:AssumeRole",
        Effect = "Allow",
        Principal = {
          Service = "amplify.amazonaws.com"
        }
      }
    ]
  })
}

# ログの書き込み設定が必要そうだったので記載
resource "aws_iam_policy" "myapp" {
  name        = "myapp"
  description = "Policy for myapp"
  policy      = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Sid       = "PushLogs",
        Effect   = "Allow",
        Action = [
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ],
        Resource = "arn:aws:logs:ap-northeast-1:${data.aws_caller_identity.current.account_id}:log-group:/aws/amplify/*:log-stream:*"
      },
      {
        Sid       = "CreateLogGroup",
        Effect   = "Allow",
        Action = [
          "logs:CreateLogGroup"
        ],
        Resource = "arn:aws:logs:ap-northeast-1:${data.aws_caller_identity.current.account_id}:log-group:/aws/amplify/*"
      },
      {
        Sid       = "DescribeLogGroups",
        Effect   = "Allow",
        Action = [
          "logs:DescribeLogGroups"
        ],
        Resource = "arn:aws:logs:ap-northeast-1:${data.aws_caller_identity.current.account_id}:log-group:*"
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "myapp" {
  role       = aws_iam_role.myapp.name
  policy_arn = aws_iam_policy.myapp.arn
}

メインのAmplifyについて記載していきます。

amplify.tf
resource "aws_amplify_app" "myapp" {
  name       = "myapp"
  repository = local.repository_url
  # githubのアクセストークンはterraform apply時にターミナル上で入力する
  access_token = var.github_access_token

  build_spec = <<EOF
    version: 1
    frontend:
      phases:
        preBuild:
          commands:
            - npm ci
        build:
          commands:
            - npm run build
      artifacts:
        baseDirectory: .next
        files:
          - '**/*'
      cache:
        paths:
          - node_modules/**/*
  EOF

  enable_auto_branch_creation = true
  enable_branch_auto_build = true
  enable_branch_auto_deletion = true
  platform = "WEB_COMPUTE"

  iam_service_role_arn = aws_iam_role.myapp.arn

  auto_branch_creation_config {
    enable_pull_request_preview = true
  }

  environment_variables = {
    # APIのURLを設定
    NEXT_PUBLIC_API_URL = local.amplify_app_environment_variables.next_public_api_url
    # カスタムイメージを設定
    _CUSTOM_IMAGE = local.amplify_app_environment_variables.custom_image
  }

  tags = {
    Name = "myapp"
  }
}

resource "aws_amplify_branch" "main" {
  app_id      = aws_amplify_app.myapp.id
  branch_name = "main"
  framework = "Next.js - SSR"

  enable_auto_build = true

  stage     = "PRODUCTION"
}

# ドメインを設定
resource "aws_amplify_domain_association" "myapp" {
  app_id      = aws_amplify_app.myapp.id
  domain_name = local.domain_name

  sub_domain {
    branch_name = aws_amplify_branch.main.branch_name
    prefix      = ""
  }

  sub_domain {
    branch_name = aws_amplify_branch.main.branch_name
    prefix      = "www"
  }
}

locals.tfは以下の通りです。

locals.tf
locals {
  repository_url = "https://github.com/yourname/myapp"
}

locals {
  amplify_app_environment_variables = {
    next_public_api_url = "https://api.myapp.com"
    custom_image = "public.ecr.aws/docker/library/node:20.11.0"
  }
}

locals {
  domain_name = "myapp.com"
}

variables.tfは以下の通りです。

variables.tf
variable "github_access_token" {}

ポイント

1. githubとの接続

githubとの接続にはaccess_tokenが必要になります。
このトークンをソースコード上で管理したくなかったため、variableとして書き出し、terraform plan/applyする際にターミナル上で入力するようにしました。
ただし組織での運用の場合面倒そうなのでより良い方法がありそうです。
ご存知の方コメントいただければ幸いです。

amplify.tf
resource "aws_amplify_app" "myapp" {
  name       = "myapp"
  repository = local.repository_url
  # githubのアクセストークンはterraform apply時にターミナル上で入力する
  access_token = var.github_access_token

  ...
}
variables.tf
variable "github_access_token" {}
ターミナル
var.github_access_token
  Enter a value: 

2. カスタムイメージの設定

2024年2月現在、Node.jsのバージョンを指定しない場合16.19.0で構築されるようです。
今回のアプリはnextバージョン:14.0.4につき、Node.jsのバージョンは18.17.0以上が求められます。
従ってDockerイメージを明示する必要があります。
そのためには環境変数_CUSTOM_IMAGEを用意し、任意のイメージを指定します。
イメージはAWSのECR Public Gallaryから探しました。
今回は20.11.0を選択しました。
https://gallery.ecr.aws/docker/library/node

amplify.tf
resource "aws_amplify_app" "myapp" {
  ...
  environment_variables = {
  ...
    # カスタムイメージを設定
    _CUSTOM_IMAGE = local.amplify_app_environment_variables.custom_image
  }
  ...
}
locals.tf
...
locals {
  amplify_app_environment_variables = {
    ...
    custom_image = "public.ecr.aws/docker/library/node:20.11.0"
  }
}
...

コンソールで確認

ここまでの内容でterraform applyするとAmplifyのリソースが作成されます。
コンソールに入ると「ビルドなし」になっていますが、「ビルド実行」ボタンを押せば開始します。
(Terraform側で何かしら設定すればこの過程は不要?)

ビルド実行ボタンを押して少し待つと、無事デプロイ成功しました🎉

なおドメインの紐付けは多少時間がかかります。
terraform applyした時点で紐付け処理は開始しています。
待っていれば完了し、任意のドメインでアクセスできるようになります。

以上です!

Discussion