0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Github Actionsを用いてVPSへNext.js製のアプリケーションをデプロイする方法

Last updated at Posted at 2025-08-13

はじめに

今回はNext.jsとtailwindcssでポートフォリオサイトを作成してGithub Actionsを用いてVPSに自動デプロイできるようにしたので,
その際の手順やymlファイルを共有します.

コードの全体像

next.confing.ts

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output:'standalone'
};
export default nextConfig;

Github Actionsのmain.yml

name: Build and Deploy to VPS
on:
  push:
    branches: [ main ]

jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    env:
      SSH_USER: ${{ secrets.SSH_USER }}
      SSH_PORT: ${{ secrets.SSH_PORT }}
      DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
      SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
      SSH_HOST: ${{ secrets.SSH_HOST }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22.17.1

      - run: npm ci
      - run: npm run build

      - name: Prepare artifact
        run: |
          mkdir -p out_artifact
          cp -ra .next/standalone/. out_artifact/
          mkdir -p out_artifact/.next/static
          cp -ra .next/static/. out_artifact/.next/static/
          cp -r public out_artifact/public || true

      - name: Setup SSH KEY
        run: |
          install -m 700 -d ~/.ssh
          echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          ssh-keyscan -p "$SSH_PORT" "$SSH_HOST" >> ~/.ssh/known_hosts

      - name: Deploy to VPS
        run: |
          set -euo pipefail
          RELEASE="${GITHUB_SHA}"

          ssh -i ~/.ssh/deploy_key -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
            "sudo install -d -o $SSH_USER -g $SSH_USER -m 775 '$DEPLOY_PATH' '$DEPLOY_PATH/releases' '$DEPLOY_PATH/releases/$RELEASE'"

          rsync -az --partial --delete --info=progress2 \
            -e "ssh -p ${SSH_PORT} -i ~/.ssh/deploy_key" \
            out_artifact/ "$SSH_USER@$SSH_HOST:$DEPLOY_PATH/releases/$RELEASE/"


      - name: Start server
        run: |
          RELEASE="${GITHUB_SHA}"
          ssh -i ~/.ssh/deploy_key -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "
          set -e
          sudo ln -sfn \"$DEPLOY_PATH/releases/$RELEASE\" \"$DEPLOY_PATH/current\"
          if pm2 describe portfolio > /dev/null 2>&1; then
            pm2 delete portfolio
          fi
          cd \"$DEPLOY_PATH/current\"
          pm2 start ./server.js --name portfolio
          pm2 save
          "

          
      - name: Delete key
        if: always()
        run: sudo rm -f ~/.ssh/deploy_key

コードの解説

next.config.ts

output:'standalone'

上記のコードを追記することでnext buildした際に最小限のファイルのみが生成されるかつそれらのファイルのみで動作が可能となります.
具体的には以下のようなディレクトリ構成のフォルダが生成されます.
起動時はserver.jsを指定することでサイトが立ち上がります.

standalone
├── node_modules
├── package.json
├── public
└── server.js

main.yml

  • 実行のトリガー
on:
  push:
    branches: [ main ]

これによりmainブランチにpushされたときに実行されるようになります.

環境変数の設定

env:
      SSH_USER: ${{ secrets.SSH_USER }}
      SSH_PORT: ${{ secrets.SSH_PORT }}
      DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
      SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
      SSH_HOST: ${{ secrets.SSH_HOST }}

これらの環境変数は当該リポジトリの「settings」→「Secrets and variables」→「Actions」→「Repository secrets」から登録できます.
ここに登録した変数は一度登録すると,登録した内容を見ることはできないので記述する際には注意して記述するようにしましょう.

SSH_USERはデプロイ先サーバのユーザ名
SSH_PORTはデプロイ先サーバにSSH接続するためのポート番号(デフォルトだと22番)
DEPLOY_PATHはデプロイ先サーバのどこにサイトの各種ファイルを置くかを指定
SSH_PRIVATE_KEYはデプロイ先サーバにSSHログインする際に使用する秘密鍵
SSH_HOSTはデプロイ先サーバにSSHログインする際のユーザ名です.

リポジトリのチェックアウト・必要なnode.jsのセットアップ・ビルド

      - name: Checkout code
        uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22.17.1

      - run: npm ci
      - run: npm run build

npm cipackage-lock.json に固定されたバージョンをもとに依存関係を解決(クリーンインストール).
npm run buildstandaloneファイルを生成してくれます.

各種ディレクトリの作成

- name: Prepare artifact
        run: |
          mkdir -p out_artifact
          cp -ra .next/standalone/. out_artifact/
          mkdir -p out_artifact/.next/static
          cp -ra .next/static/. out_artifact/.next/static/
          cp -r public out_artifact/public || true

buildした結果生成されたファイルをコピーするための各種ディレクトリを作成します.(これしなくてもできそうですよね)

SSH鍵のセットアップ

      - name: Setup SSH KEY
        run: |
          install -m 700 -d ~/.ssh
          echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          ssh-keyscan -p "$SSH_PORT" "$SSH_HOST" >> ~/.ssh/known_hosts

まず.sshディレクトリを権限700で作成します.
次に環境変数に設定した秘密鍵(SSH_PRIVATE_KEY)を登録します
登録した秘密鍵の権限を600にします.
ssh-keyscanでサーバの公開鍵フィンガープリントを取得することで以降の接続でホスト認証の確認を自動化します.(初めて接続するときにyes/noを聞かれないようにする)

VPSへファイルをコピー

      - name: Deploy to VPS
        run: |
          set -euo pipefail
          RELEASE="${GITHUB_SHA}"

          ssh -i ~/.ssh/deploy_key -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
            "sudo install -d -o $SSH_USER -g $SSH_USER -m 775 '$DEPLOY_PATH' '$DEPLOY_PATH/releases' '$DEPLOY_PATH/releases/$RELEASE'"

          rsync -az --partial --delete --info=progress2 \
            -e "ssh -p ${SSH_PORT} -i ~/.ssh/deploy_key" \
            out_artifact/ "$SSH_USER@$SSH_HOST:$DEPLOY_PATH/releases/$RELEASE/"

今回はデプロイ実行ごとにサーバー側でフォルダを分けるようにしています.具体的にはコピー先を$DEPLOY_PATH/releases/$RELEASE/${GITHUB_SHA}とすることでトリガー時のコミットハッシュ値で分けるようにしています.
まず最初にサーバ側でデプロイ先となるフォルダをパーミッションまで指定して作成します.
ファイルのコピー方法はrsyncを用いており,--deleteで転送先にあって転送元にないファイルを削除,-aなどでパーミッション・シンボリックリンク・タイムスタンプなどを保持しつつコピーしています.

起動

- name: Start server
        run: |
          RELEASE="${GITHUB_SHA}"
          ssh -i ~/.ssh/deploy_key -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "
          set -e
          sudo ln -sfn \"$DEPLOY_PATH/releases/$RELEASE\" \"$DEPLOY_PATH/current\"
          if pm2 describe portfolio > /dev/null 2>&1; then
            pm2 delete portfolio
          fi
          cd \"$DEPLOY_PATH/current\"
          pm2 start ./server.js --name portfolio
          pm2 save
          "

現在動いているバージョンを指定するためにcurrentというディレクトリを作成し,対象となるハッシュ値を持つディレクトリからシンボリックリンクを張っています.
サーバの起動はpm2を使用しています.過去にすでにデプロイされていた場合(if pm2 describe portfolio > /dev/null 2>&1; then)にpm2 reloadではなく,pm2 deleteしているのは,今回シンボリックリンクを張っているcurrentディレクトリを指定して起動していることから,2度目のデプロイの際にcurrentディレクトリの中身は変わっても,pm2が示している実行ファイル(server.js)のパスが過去のものを指してしまうということを防ぐためです.(回りくどいコードになってしまいましたが・・・)

秘密鍵の削除

- name: Delete key
        if: always()
        run: sudo rm -f ~/.ssh/deploy_key

最後に秘密鍵を削除します.ここでif: always()とすることで,テストが失敗したかどうかを問わず鍵を削除するようにします.

最後に

自身の勉強の記録を残す場所,アウトプットする場所としてポートフォリオを充実させていくことはキャリアを通じても大事なことだと思います.
この記事が誰かの参考になれば幸いです.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?