🚀

Next.jsをAWS EC2にデプロイする方法(さくらのドメイン、さくらのメール)

2024/07/07に公開

YouTube でゼロから開発するプログラミング動画チュートリアル「フルスタックチャンネル」を発信中の「はる」と申します。

フルスタックチャンネル

Next.js を AWS EC2 にデプロイする方法を簡単に紹介します。

Docker を使用せずに、Next.js を EC2 にデプロイする方法になります。

GitHub Actions を使用して、自動デプロイも行います。

ドメインは、さくらのドメインを使用して、さくらのメールの設定も行います。

構成

こちらの Next.js バージョンです。

https://zenn.dev/hathle/articles/django-ec2-deploy

IAM 作成

IAM でユーザーを作成します。

FullAccess でポリシーを許可していますが、必要な権限だけ設定するようにしてください。

S3 バケット作成

必要に応じて S3 を作成します。

VPC 作成

VPC を作成します。

アベイラビリティーゾーン数は必要に応じて変更してください。

セキュリティグループ作成

セキュリティグループを作成します。

インバウンドルールで下記のポートをあけます

  • SSH
  • HTTP
  • HTTPS
  • PostgreSQL

EC2 インスタンス作成

先ほど作成した VPC とセキュリティグループを連携します。

サブネットは public1-ap-northeast-1a を指定

Ubuntu Server 22.04

インスタンス:t2.micro

t2.micro はかなり遅いので、必要に応じて変更してください。

オススメ:t3.medium

RDS 作成

PostgreSQL を使用します。

先ほど作成した EC2 と連携します。

インスタンス:t3.micro

Elastic IP 作成

Elastic IP を作成して、EC2 に紐付けます。

先に Elastic IP を作成しておくと、IP アドレスが固定されるので、便利です。

Next.js プロジェクト作成

Next.js プロジェクトを作成して、GitHub にアップロードしておきます。

SSH 接続

EC2 を作成したときに作成した xxx.pem を使用して SSH 接続します。

ssh -i "~/.ssh/xxxxx.pem" ubuntu@ec2-xxxxx.ap-northeast-1.compute.amazonaws.com

RDS 接続

SSH 接続ができることを確認したら、RDS のデータベースを作成します。

RDS はプライベートサブネットに作成しているので、EC2 から RDS に接続します。

ローカルから接続したい場合は、以下のコマンドを実行します。

ssh -i ~/.ssh/xxxxx.pem -L 6000:xxxxx.xxxxx.ap-northeast-1.rds.amazonaws.com:5432 -N -T ubuntu@xx.xxx.xxx.xx

SQL Shell は便利なので、使用しています。

https://www.postgresql.org/download/

・解説
https://zenn.dev/hathle/books/t3-stack-auth-book/viewer/06_postgresql

データベースを作成します。

create database xxxxx;

Node.js インストール

https://github.com/nvm-sh/nvm

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm
nvm install --lts
node -v
npm i -g pm2
sudo apt-get update
sudo apt-get install -y nginx
sudo systemctl start nginx
sudo service nginx restart

この時点で、EC2 の IP アドレスにアクセスして、Welcome to nginx! が表示されることを確認します。

リポジトリクローン

GitHub からリポジトリをクローンします。

git clone https://github.com/xxx/xxxxxx.git
cd xxxxxx

Github の remote 先を変更

Github Actions から git pull するためには、origin をgit@github.comにする必要があります。

git remote -v
origin  https://github.com/xxx/xxxxxx.git

git remote set-url origin git@github.com:xxx/xxxxxx.git

環境変数作成

.envファイルを作成します。

データーベースは RDS の接続先情報を設定します。

必要に応じて変更してください。

nano .env
DATABASE_URL=postgresql://postgres:{RDSのパスワード}@{RDSのエンドポイント}:5432/{作成したデータベース名}?schema=public
NEXTAUTH_SECRET=
NEXTAUTH_URL=http://{EC2のIPアドレス}
NEXT_PUBLIC_APP_URL={EC2のIPアドレス}
EMAIL=
EMAIL_HOST=
EMAIL_PASSWORD=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=
AWS_BUCKET_NAME=

Nginx 設定

Nginx の設定を行って、画面を表示します。

sudo nano /etc/nginx/conf.d/xxx-production.conf
server {
    listen 80;
    server_name {EC2のIPアドレス};
    client_max_body_size 10g;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
sudo nginx -t
sudo systemctl restart nginx

この時点で、EC2 の IP アドレスにアクセスして、502 Bad Gateway が表示されることを確認します。

セットアップ

Next.js のセットアップを行います。

npm install
npx prisma migrate deploy
npm run build
pm2 start 'npm start' --name app
pm2 log

これで、EC2 の IP アドレスにアクセスして、Next.js の画面が表示されることを確認します。

SSH 公開鍵作成

Github Actions からデプロイをするために、ssh 公開鍵を作成します。

ssh-keygen -t rsa -b 4096 -C "your_email@gmail.com"

# 公開鍵を登録
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

Deploy keys

Github の Settings の Deploy keys に id_rsa.pub の公開鍵を登録します。

設定からSSH and GPG keysをクリックしてアクセスします。

ここに登録しないと、EC2 で git pull できません。

Github Actions 環境変数

Github の Secret and variables の Actions に環境変数を追加します。

HOST_PROD ec2-xx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com
USERNAME_PROD ubuntu
SSH_KEY_PROD  id_rsaの中身
AWS_ACCESS_KEY_ID xxx
AWS_SECRET_ACCESS_KEY xxx

GitHub Actions

Github Actions から git pull して、リスタートまでを自動的に実行します。

.github/workflows/deploy.yml

name: Deploy to EC2 Production

on:
  push:
    branches:
      - release/production

jobs:
  build-and-deploy:
    name: Build and Deploy to EC2
    runs-on: ubuntu-22.04

    steps:
    - name: Checkout to the branch
      uses: actions/checkout@v2

    - 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: ap-northeast-1

    - name: Deploy to EC2
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.HOST_PROD }}
        username: ${{ secrets.USERNAME_PROD }}
        key: ${{ secrets.SSH_KEY_PROD }}
        script: |
          cd /home/ubuntu/xxxx
          git pull
          npm install
          npx prisma migrate deploy
          npm run build
          pm2 del app
          pm2 start 'npm start' --name app

ドメイン取得

さくらのドメインでドメインを取得します。

https://domain.sakura.ad.jp/

AWS Route53

Route53 でドメインを設定します。

さくらのドメインで取得したドメインを登録します。

NS レコードを 4 つコピーします。

最後の.は削除してください。

さくらのドメインでネームサーバー設定

さくらのドメインで、取得したドメインの Whois を設定します。

Whois のタブでネームサーバーを編集をします。

ネームサーバーの反映は少し時間がかかるかもしれません。

さくらのレンタルサーバ

さくらのレンタルサーバで、ドメインを設定します。

ドメイン新規追加画面に遷移します。

さくらインターネットで取得の独自ドメインを使う場合で、ドメインを追加します。

ゾーン情報でゾーンが設定されていることを確認します。

さくらのメール設定

さくらのレンタルサーバーでメールドレスの新規ユーザーを追加します。

Route53 MX レコード設定

Route53 で MX レコードを設定します。

さくらのレンタルサーバーのサーバー情報のホスト名をコピーして設定します。

10 ホスト名

Route53 TXT レコード設定

Route53 で TXT レコードを設定します。

さくらのレンタルサーバーのサーバー情報の IPv4 をコピーして設定します。

"v=spf1 ip4:xxx.xxx.x.xx -all"

Route53 A レコード設定

Route53 で A レコードを設定します。

EC2 に割り当てた Elastic IP を設定します。

xxx.xxx.x.xx

以上で Route53 の設定は完了です。

設定確認

レコードの設定が正しいか確認します。

ネームサーバーの反映が完了しているか確認しておきます。

dis xxxx.com ns +short
dig xxxx.com mx +short
dig xxxx.com txt +short

HTTPS 化

Let's Encrypt を使用します。

sudo apt-get install letsencrypt
sudo systemctl stop nginx
sudo letsencrypt certonly --standalone -d xxxx.com
sudo nano /etc/nginx/conf.d/xxx-production.conf

nginx の設定を変更します。

server {
    listen 80;
    listen [::]:80;
    server_name xxxx.com;
    return 301 https://$host$request_uri;
}

server {
    listen  443 ssl;
    server_name xxxx.com;
    client_max_body_size 10g;

    ssl_certificate         /etc/letsencrypt/live/xxxx.com/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/xxxx.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

nginx のチェック

sudo nginx -t
sudo systemctl start nginx
pm2 del app
pm2 start 'npm start' --name app

動かない場合

nginx のエラーログを確認します。

sudo tail -f /var/log/nginx/error.log
sudo ufw allow 80
sudo ufw allow 443
curl http://127.0.0.1:3000
sudo systemctl restart nginx

証明書自動発行

定期的に証明書を更新するために、cron を使用します。

sudo crontab -e
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/
00 05 01 * * sudo systemctl stop nginx; sudo letsencrypt renew; sudo systemctl start nginx

nodemailer を使用してメール送信

さくらのレンタルサーバーのメールを使用する場合です。

.env ファイルにメールの設定を追加します。

EMAIL_HOST は、さくらのレンタルサーバーのホスト名です。

EMAIL=test@xxxx.com
EMAIL_HOST=xxxx.sakura.ne.jp
EMAIL_PASSWORD=xxxxxxxxxxxxxxxx
import nodemailer from "nodemailer"

// SMTPサーバの設定
const transporter = nodemailer.createTransport({
  host: process.env.EMAIL_HOST!,
  pool: true,
  port: 587,
  secure: false,
  requireTLS: true,
  auth: {
    user: process.env.EMAIL!,
    pass: process.env.EMAIL_PASSWORD!,
  },
})

// メール送信
export const sendEmail = async (
  subject: string,
  body: string,
  sendTo: string
) => {
  const mailOptions = {
    // 送信元
    from: `テスト <${process.env.EMAIL}>`,
    // 送信先
    to: sendTo,
    // 件名
    subject: subject,
    // 本文
    html: body,
  }

  // メール送信
  transporter.sendMail(mailOptions, (error) => {
    if (error) {
      console.log(error)
    }
  })
}

使用例

パスワード再発行メール送信の例です。

import { sendEmail } from "@/actions/sendEmail"
import { db } from "@/lib/prisma"

interface SendForgotPasswordOptions {
  userId: string
}

// パスワード再発行メール送信
export const sendForgotPassword = async ({
  userId,
}: SendForgotPasswordOptions) => {
  try {
    // ユーザー取得
    const user = await db.user.findUnique({
      where: {
        id: userId,
      },
      include: {
        PasswordResetToken: {
          orderBy: {
            createdAt: "desc",
          },
          take: 1,
        },
      },
    })

    if (!user || !user.email) {
      return { error: "ユーザーが存在しません" }
    }

    // トークン取得
    const token = user.PasswordResetToken[0].token

    // パスワード再発行リンク
    const resetPasswordLink = `${process.env.NEXT_PUBLIC_APP_URL}/reset-password/${token}`

    // 件名
    const subject = "パスワード再発行のご案内"

    // 本文
    const body = `
<div>
  <p>
    ご利用ありがとうございます。<br />
    あなたのアカウントでパスワード再発行のリクエストがありました。
  </p>

  <p><a href="${resetPasswordLink}">パスワードの再発行を行う</a></p>

  <p>このリンクの有効期限は24時間です。</p>
  <p>このメールに覚えのない場合は、このメールを無視するか削除して頂ますようお願いします。</p>
</div>
 `

    // メール送信
    await sendEmail(subject, body, user.email)
    return { success: "パスワード再発行用のメールをお送りしました" }
  } catch (error: any) {
    console.error("[SEND_FORGOT_PASSWORD]", error)
    return { error: "メール送信中にエラーが発生しました" }
  }
}

ソースコード

LINE 登録すると講座のソースコードがダウンロードできます。

新着の講座がいち早く届きますので、ぜひ登録をお願いします。

LINE お友達登録
https://lin.ee/NKoTJnV

フルスタックチャンネル

講座の不明点や個人開発の疑問点など何でもご質問ください。

https://www.fullstackchannel.com/

さらに多くの知識と技術を共有し、また新しい学びの機会を提供することを楽しみにしています。

よろしくお願いします。

Discussion