Next.js のプロジェクトを VPS 上の Docker にデプロイする【その5】

Next.js のプロジェクトを VPS 上の Docker にデプロイする【その1】【その2】【その3】【その4】の続きです。

前回、遂に、自動で Next.js のプロジェクトを VPS 上の Docker にデプロイすることができました。

今回は開発・運用がしやすいワークフローを目指します。実際に独自ドメインの設定も行う予定です。

開発・運用のフローを考える

前回は、main ブランチに push されると GitHub Container Registry にビルドし、VPS にデプロイするワークフローを作成しました。

実運用するにあたり、図のようにステージング環境を用意していこうと思います。

ブランチ

ブランチは以下のように設定することにします。

  • main: 本番環境
  • staging: ステージング環境
  • develop: 開発環境

役割

ステージング環境は、本番環境とポートとドメインのみを変更したものにします。
開発環境は、ローカル環境ですぐに変更が確認できる状態のものにします。

今回は以下のドメインでアクセスできるようにします。

  • main: donuts.cookin.dev に配置
  • staging: donuts-stagnig.cookin.dev に配置
  • develop: http://localhost:3000(ローカルPC上)

Dockerfile ファイル

各環境で、Dockerfileを以下の名前で用意します。

  • main: prod.Dockerfile
  • staging: stg.Dockerfile
  • develop: dev.Dockerfile

Docker Compose の利用

Docker Compose を利用するのは develop 環境のみになります。

  • main: なし
  • staging: なし
  • develop: docker-compose.dev.yml

GitHub Actions のワークフローの作成

GitHub Actions のワークフローは本番環境とステージング環境でそれぞれ作成します。

  • main: deploy.prod.yml
  • staging: deploy.stg.yml
  • develop: なし

各ブランチの作成

上記に合わせてブランチの作成を行います。

# 作業ディレクトリに移動
$ cd ~/NextProjects/donuts-site

# staging ブランチの作成とリモートへの反映
$ git branch staging
$ git push origin staging

# develop ブランチの作成とリモートへの反映
$ git branch develop
$ git push origin develop

# develop にチェックアウトしておく
$ git checkout develop

各ファイルの用意

上記を元にファイルを作成します。
deploy.stg.ymldeploy.prod.ymlを作成して、以下のようなフォルダ構成になりました。

.
├── .eslintrc.json
├── .git
├── .github
│   └── workflows
│       ├── deploy.prod.yml
│       └── deploy.stg.yml
├── .gitignore
├── .next
├── README.md
├── dev.Dockerfile
├── docker-compose.dev.yml
├── next-env.d.ts
├── next.config.js
├── package.json
├── pages
├── prod.Dockerfile
├── public
├── styles
├── tsconfig.json
└── yarn.lock

ステージング環境のDockerfile

ステージング環境用のstg.Dockerfileの中身を書きます。

ポート番号を 49152-65535番ポート(ダイナミック/プライベートポート番号)から適当に決めて設定します。

stg.Dockerfile
##### deps ステージ #####
FROM node:16-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
  if [ -f yarn.lock ]; then yarn install --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm ci; \
  elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \
  else echo "Lockfile not found." && exit 1; \
  fi

##### builder ステージ #####
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN yarn build

##### runner ステージ #####
FROM node:16-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 49200

ENV PORT 49200

CMD ["node", "server.js"]

ステージング環境のワークフロー

次にステージング環境用のワークフローを.github/workflows/deploy.stg.ymlに書きます。

先ほど指定したポート番号とDockerfileを指定し、ステージング用にイメージを作成するようにします。

実行タイミングは、stagingブランチにプッシュされた場合にします。

deploy.stg.yml
name: Deploy to Sakura VPS Staging

# staging ブランチにプッシュされた時に実行する
on: 
  push:
    branches: [staging]
  # 手動実行時のログを設定
  workflow_dispatch:
    inputs:
      logLevel:
        description: 'Log level'
        required: true
        default: 'warning'
jobs:
  deploy:
    runs-on: ubuntu-latest
    container: node:16
    steps:
      # チェックアウト
      - uses: actions/checkout@v3
      # Github Container Registry へビルドしてイメージを格納
      - name: Build and Publish to Github Container Registry
        uses: elgohr/Publish-Docker-Github-Action@master
        with:
          # 本番とステージングでイメージ名を変更
          name: GitHubのユーザ名(lowercase)/donutssite/donuts-site-image-staging
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
          dockerfile: stg.Dockerfile
          tags: latest
      # VPSへのデプロイ
      - name: Deploy to Sakura VPS
        uses: appleboy/ssh-action@master
        env:
          GITHUB_USERNAME: ${{ github.actor }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          port: ${{ secrets.DEPLOY_PORT }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_KEY }}
          envs: GITHUB_USERNAME, GITHUB_TOKEN
          script: |
            docker login ghcr.io -u $GITHUB_USERNAME -p $GITHUB_TOKEN
            docker image pull ghcr.io/GitHubのユーザ名(lowercase)/donutssite/donuts-site-image-staging:latest
            docker container stop donuts-site
            docker container rm donuts-site
            docker system prune -f
            docker container run --name donuts-site -dit -p 49200:49200 --restart=always ghcr.io/GitHubのユーザ名(lowercase)/donutssite/donuts-site-image-staging:latest

GitHub にプッシュする

developブランチから上記の変更をプッシュしておきます。

# 作業ディレクトリに移動する
$ cd ~/NextProjects/donuts-site

$ git add .
$ git commit -m ":rocket: Add staging environment"
$ git push origin develop

develop ブランチに変更があってもアクションが実行されないことを確認しておきます。

developブランチをstagingブランチにマージします。

staging ブランチのワークフローが動き出します。

デプロイが完了したので http://IPアドレス:49200 へアクセスしてみます。(ポート番号は先ほど指定したもの)

無事に表示されました😊

続いて、staging ブランチを main にマージします。

main ブランチのポートも適当に変更しました。http://IPアドレス:本番環境のポート へアクセスして表示されればOKです。

これでステージング環境の構築ができました。

独自ドメインの設定

以下を前提として設定してきます。

  • VPS(Ubuntu)上に nginx の設定がされている
  • ドメインが取得されている
STEP.1
独自ドメインを利用できるようにする
まずは独自ドメインをさくらVPSのコンソールより設定します。ネームサーバー等の設定は割愛します。
今回はこのドメインのサブドメインを設定してdonuts.cookin.devを本番環境とします。

STEP.2
サーバーブロックを作成する
次に nginx のサーバブロックを作成します。

# sites-available ディレクトリにドメイン名のファイルを作成する
% sudo vi /etc/nginx/sites-available/donuts.cookin.dev
donuts.cookin.dev
server {

  listen 80;
  server_name donuts.cookin.dev;
  location / {
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Proto http;
    proxy_pass http://localhost:本番環境のポート;
  }

}
# Nginxが起動時に読み取るsites-enabledディレクトリにリンクを作成して、ファイルを有効にする
% sudo ln -s /etc/nginx/sites-available/donuts.cookin.dev /etc/nginx/sites-enabled/

# nginx.conf の構文が正しいかをチェックする
% sudo nginx -t

# サーバを再起動する
% sudo systemctl restart nginx

ドメインが反映されるまで時間がかかるのでしばらく待ちます。
http://donuts.cookin.dev/ にアクセスして無事に反映されました。

STEP.3
SSL化する

最後にCertbotを使って証明書を発行し、SSL化しておきます。

# Certbot と Nginx プラグインをインストール
% sudo apt install certbot python3-certbot-nginx

# Nginx プラグインで Certbot を実行
% sudo certbot --nginx -d donuts.cookin.dev

# リダイレクトの設定をする (2)
Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2

# 証明書の自動更新の確認
% sudo systemctl status certbot.timer

# 証明書の更新テスト
% sudo certbot renew --dry-run

これによりsites-availableに配置したサーバブロックの設定が変更されます。

donuts.cookin.dev
server {
    server_name donuts.cookin.dev;
    location / {
      proxy_set_header Host $http_host;
      proxy_set_header X-Forwarded-Proto https;
      proxy_pass http://localhost:本番のポート番号;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/donuts.cookin.dev/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/donuts.cookin.dev/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = donuts.cookin.dev) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80;
    server_name donuts.cookin.dev;
      return 404; # managed by Certbot
}

ステージング環境のドメイン設定

ステージング環境も同様に設定していきます。donuts-staging.cookin.devとしました。

# sites-available ディレクトリにドメイン名のファイルを作成する
% sudo vi /etc/nginx/sites-available/donuts-staging.cookin.dev
donuts-staging.cookin.dev
server {

  listen 80;
  server_name donuts.cookin.dev;
  location / {
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Proto http;
    proxy_pass http://localhost:49200;
  }

}
# Nginxが起動時に読み取るsites-enabledディレクトリにリンクを作成して、ファイルを有効にする
% sudo ln -s /etc/nginx/sites-available/donuts-staging.cookin.dev /etc/nginx/sites-enabled/

# nginx.conf の構文が正しいかをチェックする
% sudo nginx -t

# サーバを再起動する
% sudo systemctl restart nginx

# Nginx プラグインで Certbot を実行
% sudo certbot --nginx -d donuts-staging.cookin.dev

# リダイレクトの設定をする (2)
Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2

これで https://donuts-staging.cookin.dev で確認できるようになりました。

おわりに

今回で、Next.js + Docker + VPS での開発・運用環境の構築奮闘記は終わりになります。
参考記事などでは1記事でまとまっているようなことを、10記事以上かけてじっくり取り組んできました。

何度も手戻りが発生していて参考にはなりづらいと思うので、まとめ版記事も用意する予定です。