Next.js のプロジェクトを VPS 上の Docker にデプロイする【その4】
Next.js のプロジェクトを VPS 上の Docker にデプロイする【その1】、【その2】、【その3】の続きです。
前回は実際に本番環境へのデプロイを行いました。
しかし、前回の GitHub のリポジトリを本番環境へ直接 pull するやり方では懸念点が多いため、今回はよりよいデプロイ方法について追求していこうと思います。
デプロイ方法の調査
Next.js プロジェクトを VPS 上の Docker へデプロイする方法について改めて調査してみます。
実際にやりたいことは「Deploying Next.js apps to a VPS using Github actions and Docker」の記事通りなので、今回は主にこの記事を参考に考えていきます。
上記記事の方法では、GitHub Actions と GitHub Container Registry を使って、自動で GitHub のリポジトリに変更があった場合(プッシュやマージ等)に VPS 上の Docker にデプロイしています。
GitHub Actionsは CI / CDツールです。ソフトウェア開発のワークフローをリポジトリの中で自動化して、実行することができます。
例えばリポジトリに対してプッシュがあった時に、自動でビルドを開始する、テストを行う等のアクションを自動化することが可能になります。
GitHub Container Registryは Docker イメージのレジストリ(保管場所)です。
GitHub が提供するレジストリなので、GitHub Actions との組み合わせが行いやすくなります。
docker login
コマンドで、GitHub の Personal Token を使って認証を行うので、他のサービスとの連携等が不要になります。
参考記事のコードを読む
「Deploying Next.js apps to a VPS using Github actions and Docker」の GitHub Actions の自動化コードを読んで、実際にどのようなことを行っているのか調査してみます。
GitHub Actions のドキュメントをみながらdeploy.yml
の中身を読みます。
# name: GitHub リポジトリの [アクション] タブに表示されるワークフローの名前(省略可能)
name: Build and Deploy
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the main branch
# on: ワークフローのトリガーを指定
on:
push:
branches: [main]
workflow_dispatch:
inputs:
logLevel:
description: 'Log level'
required: true
default: 'warning'
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
# ワークフローで実行されるすべてのジョブをグループ化
jobs:
# This workflow contains a single job called "build"
# 任意の名前のジョブを定義
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
container: node:14
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Build and Publish to Github Packages Registry
uses: elgohr/Publish-Docker-Github-Action@master
env:
NEXT_PUBLIC_BACKEND_URL: ${{ secrets.APP_NEXT_PUBLIC_BACKEND_URL }}
NEXT_PUBLIC_META_API_KEY: ${{ secrets.APP_NEXT_PUBLIC_META_API_KEY }}
with:
name: my_github_username/my_repository_name/my_image_name
registry: ghcr.io
username: ${{ secrets.USERNAME }}
password: ${{ secrets. GITHUB_TOKEN }}
dockerfile: Dockerfile
buildargs: NEXT_PUBLIC_BACKEND_URL,NEXT_PUBLIC_META_API_KEY
tags: latest
- name: Deploy package to digitalocean
uses: appleboy/ssh-action@master
env:
GITHUB_USERNAME: ${{ secrets.USERNAME }}
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 pull ghcr.io/my_github_username/my_repository_name/my_image_name:latest
docker stop containername
docker system prune -f
docker run --name containername -dit -p 3000:3000 ghcr.io/my_github_username/my_repository_name/my_image_name:latest
大まかな構成
アクションを追加したいリポジトリに.github/workflows/
ディレクトリを作成し、yaml ファイルを作成します。
まずは、name
, on
, jobs
に分割してみていきます。
それぞれ以下を定義しています。
# name: GitHub リポジトリの [アクション] タブに表示されるワークフローの名前(省略可能)
name: Build and Deploy
# on: ワークフローのトリガーを指定
on:
# ワークフローで実行されるすべてのジョブをグループ化
jobs:
トリガー指定
onはどのタイミングで jobs を実行するかを定義します。
on に書かれている概要は以下の通りです。
- メインブランチにプッシュされた時
- 手動実行時にどの粒度のログを表示させるかについての設定を行う
on:
# main ブランチにプッシュされた時
push:
branches: [main]
workflow_dispatch:
inputs:
logLevel:
description: 'Log level'
required: true
default: 'warning'
workflow_dispatchはワークフローを手動でトリガーする場合に利用します。
ブラウザからワークフローを実行する場合に、ワークフローを実行する前に、必要な値を手動で入力する必要があります。
ジョブの指定
jobs ではワークフローで実際に行いたいアクションを定義します。
build
という名前のジョブを定義して、実行環境や実行内容を定義しています。
steps
では、ジョブで実行されるすべてのステップをグループ化します。
steps
で入れ子になった各項目は、個別のアクションです。
# ワークフローで実行されるすべてのジョブをグループ化
jobs:
# 任意の名前のジョブを定義
build:
# Ubuntu Linux ランナーの最新バージョンで実行されるようにジョブを構成
runs-on: ubuntu-latest
# 実行環境のコンテナイメージを指定
container: node:14
# ジョブで実行されるすべてのステップをグループ化します。
steps:
jobs.{job_id}.runs-onでジョブを実行する環境の定義ができます。
実行環境の種類は、GitHub ホステッド ランナーの選択にて確認することができます。
また、jobs.{job_id}.containerを設定することで、コンテナを作成して実行できるようになります。
jobs.{job_id}.runs-on
で指定した Ubuntu Linux ランナー上の Docker Engine 上に
jobs.{job_id}.container
で指定したイメージよりコンテナを作成します。
steps はこの作成されたコンテナ内で実行されます。
Steps
steps
の中身を見てみます。
jobs.build の中には、3つのステップが定義されています。
usesでは、ジョブでステップの一部として実行されるアクションを選択します。
uses: actions/checkout@v2
でアクションを使用する前にリポジトリをチェックアウトする必要があるそうです。ここ難しい。
# STEPワークフローと同じリポジトリにあるアクションの使用
- uses: actions/checkout@v2
elgohr/Publish-Docker-Github-Actionアクションを使って、GitHub Container Registry にビルドしていきます。
env
でビルド時に利用する環境変数の指定ができます。
with
で、入力パラメータを設定します。
ここで入力する内容は各アクションによって異なります。
今回利用しているelgohr/Publish-Docker-Github-Actionで指定されている各パラメータの設定を行います。
# ステップに名前をつける
- name: Build and Publish to Github Packages Registry
# パブリック アクションの使用
uses: elgohr/Publish-Docker-Github-Action@master
# 環境変数を設定
env:
NEXT_PUBLIC_BACKEND_URL: ${{ secrets.APP_NEXT_PUBLIC_BACKEND_URL }}
NEXT_PUBLIC_META_API_KEY: ${{ secrets.APP_NEXT_PUBLIC_META_API_KEY }}
# アクションによって定義される入力パラメーターを設定
with:
# イメージ名
name: my_github_username/my_repository_name/my_image_name
# イメージを格納したいレジストリ(GitHub Packages Container Registryを指定)
registry: ghcr.io
# GitHub のユーザ名
username: ${{ secrets.USERNAME }}
# GitHub のトークン
password: ${{ secrets.GITHUB_TOKEN }}
# Dockerfile の指定
dockerfile: Dockerfile
# 上記 env で指定した環境変数を利用
buildargs: NEXT_PUBLIC_BACKEND_URL,NEXT_PUBLIC_META_API_KEY
# イメージのタグを指定
tags: latest
環境変数等の secrets の設定は、GitHub 上で設定ができます。
.env
ファイルに書いている内容等を GitHub 上で指定してビルドを行います。
自サーバーに直接.env
ファイルを置かなくていいので、セキュリティ的にも安心感があります。
次にVPS(記事の例ではDigitalOcean)へのデプロイを行います。
SSH接続をするためにappleboy/ssh-action@masterアクションを利用します。
VPS にSSH接続をして、STEP.2 でビルドしたイメージを pull してからコンテナを作り直すスクリプトが書かれています。
# ステップに名前をつける
- name: Deploy package to digitalocean
# パブリックアクションの使用
uses: appleboy/ssh-action@master
# 環境変数の設定
env:
GITHUB_USERNAME: ${{ secrets.USERNAME }}
GITHUB_TOKEN: ${{ secrets. GITHUB_TOKEN }}
# アクションによって定義される入力パラメーターを設定
with:
# デプロイ先のホストの指定
host: ${{ secrets.DEPLOY_HOST }}
# デプロイ先のポート番号の指定
port: ${{ secrets.DEPLOY_PORT }}
# デプロイ先のユーザを指定
username: ${{ secrets.DEPLOY_USER }}
# デプロイ先のキーを指定(SSH接続の秘密鍵)
key: ${{ secrets.DEPLOY_KEY }}
# 利用する環境変数の指定
envs: GITHUB_USERNAME, GITHUB_TOKEN
# 接続後に行うコマンド
script: |
docker login ghcr.io -u $GITHUB_USERNAME -p $GITHUB_TOKEN
docker pull ghcr.io/my_github_username/my_repository_name/my_image_name:latest
docker stop containername
docker system prune -f
docker run --name containername -dit -p 3000:3000 ghcr.io/my_github_username/my_repository_name/my_image_name:latest
ワークフローを定義する
さて、参考記事の自動化コードを読んで、なんとなくわかった気になったので実際にワークフローを書いていきます。
# 作業ディレクトリに移動する
$ cd ~/NextProjects/donuts-site
# ワークフローを配置するディレクトリを作成
$ mkdir -p .github/workflows/
# ワークフローファイルを作成する
$ touch .github/workflows/deploy-vps.yml
イメージのビルドには、Docker 公式が提供しているdocker/login-actionとdocker/build-push-actionを用いてみました。
name: Deploy to Sakura VPS
# main ブランチにプッシュされた時に実行する
on:
push:
branches: [main]
# 手動実行時のログを設定
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: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# イメージのビルド
- uses: docker/build-push-action@v3
with:
context: .
# マルチステージビルドでのステージ名
target: runner
push: true
tags: ghcr.io/<GitHubのユーザ名>/donuts-site-image:latest
# VPSへのデプロイ
- name: Deploy to Sakura VPS
uses: appleboy/ssh-action@master
env:
GITHUB_USERNAME: ${{ secrets.USERNAME }}
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のユーザ名>/DonutsSite/donuts-site-image:latest
docker container stop donuts-site
docker container rm donuts-site
docker system prune -f
docker container run --name donuts-site -dit -p 3000:3000 ghcr.io/<GitHubのユーザ名>/DonutsSite/donuts-site-image:latest
書いたら保存して GitHub にプッシュしておきます。
GitHub 上で Action タブよりワークフローが実行されたことを確認します。
secrets の設定をする
現状では secrets の設定をしていないので上記画像のようにエラーが起きています。
リポジトリの Settings > Secrets > Actions に必要な secrets を指定していきます。
以下の4つを設定します。
# さくらVPSのIPアドレス
DEPLOY_HOST
# SSH接続用のポート
DEPLOY_PORT
# SSH接続するユーザ
DEPLOY_USER
# SSH接続の秘密鍵
DEPLOY_KEY
GitHub のトークン(GITHUB_TOKEN
)は自動生成されるので設定は必要ありません。
Actions タブより再度実行してみました。
エラーが出ました。
Error: Unable to locate executable file: docker. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.
まず、docker/login-action
を使うにはdocker/setup-buildx-actionを使ってねとのことなので追記します。(参考:「https://dev.classmethod.jp/articles/github-actions-docker-build-ecr/)
jobs:
deploy:
runs-on: ubuntu-latest
container: node:16
steps:
# チェックアウト
- uses: actions/checkout@v3
# Docker Buildx のセットアップ
- uses: docker/setup-buildx-action@v1
上記エラーは消えないのでもう少し調べてみたところ、GitHub Container Registry を使う場合には 一度 ghcr.io にログインする必要があるようです。(参考:GitHub Container Registry(ghcr.io)にDockerイメージをpushする手順)
上記エラーを一度もみ消して、参考記事通りにelgohr/Publish-Docker-Github-Actionを使ってみます。
jobs:
deploy:
runs-on: ubuntu-latest
container: node:16
steps:
# チェックアウト
- uses: actions/checkout@v3
- name: Build and Publish to Github Container Registry
uses: elgohr/Publish-Docker-Github-Action@master
with:
name: <GitHubのユーザ名>/DonutsSite/donuts-site-image
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
dockerfile: prod.Dockerfile
tags: latest
SSH 接続できるようにする
GitHub Container Registry へのイメージのビルドと公開には成功しました。次は VPS へのデプロイでエラーが出ています。
GitHub Container Registry に格納されたイメージは、リポジトリの右側のメニューにあるpackagesより確認することができます。
SSH接続で失敗しているので改めて、appleboy/ssh-action の README.mdの確認をします。
どうやら秘密鍵と公開鍵が逆かもしれない…手順通りに生成してみます。
まずは、ローカルPCでキーの作成を行います。
# キーの作成をする
$ ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
生成された秘密鍵の方を 先ほどの GitHub のリポジトリの secrets に登録します。
公開鍵は VPS 上に配置します。
# VPSにログインする
$ ssh -p ポート番号 ユーザ名@IPアドレス
# ~/.ssh に配置
% cd ~/.ssh
% vi authorized_keys2
# ペーストする
# 権限を変更する
% chmod 700 ~/.ssh
% chmod 640 ~/.ssh/authorized_keys2
再度ワークフローの実行をしてみます。
Actions > 失敗したアクション > Re-run failed jobs を選択します。
ログインに成功しましたがまだエラーが出ています。
err: docker: invalid reference format: repository name must be lowercase.
Docker のイメージの名前とユーザ名は lowercase で書く必要があるそうなので変更します。
正しいイメージ名に変更したところ、無事に成功しました😊
VPSに接続してコンテナの稼働を確認する
最後にVPSに接続してコンテナを確認してみましょう。
# VPSにログインする
$ ssh -p ポート番号 ユーザ名@IPアドレス
# コンテナの確認をする
% docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
61a7e88d0003 ghcr.io/ユーザ名/donutssite/donuts-site-image:latest "docker-entrypoint.s…" 22 minutes ago Up 22 minutes 0.0.0.0:3000->3000/tcp donuts-site
# イメージの確認をする
% docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ghcr.io/ユーザ名/donutssite/donuts-site-image latest 010e2e102c13 7 minutes ago 121MB
無事にコンテナの確認ができました。http://IPアドレス:3000 にアクセスしてサイトも表示されました😊
最後に、スタイルの変更をして main ブランチに push して変更が自動で反映されるか確認してみたところ、無事に変更がされました。
最終的なワークフロー
最後に Docker Compsoe 時に使用していたrestart: always
の指定を追記しました。
name: Deploy to Sakura VPS
# main ブランチにプッシュされた時に実行する
on:
push:
branches: [main]
# 手動実行時のログを設定
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
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
dockerfile: prod.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:latest
docker container stop donuts-site
docker container rm donuts-site
docker system prune -f
docker container run --name donuts-site -dit -p 3000:3000 --restart=always ghcr.io/GitHubのユーザ名(lowercase)/donutssite/donuts-site-image:latest
おわりに
無事にデプロイを自動化することに成功しました。
結果的に Docker Compose も使わなくなってしまいましたが、目標であった自動デプロイまでできました!
次回は、独自ドメインの設定とポート番号の変更等、最終的な調整をしていこうと思います。