GitHub CI/CD で VPS へ自動デプロイするまで

著者画像
Toshihiko Arai

はじめに

2025年の今回初めてGitHubのCI/CDを使ってみてとても便利だったので、やり方・手順を忘備録として残しました。

今回対象となるプロジェクトは個人で開発している小さなjsライブラリプロジェクトです。

GitHubで管理・公開しているこのnu.jsにタグをつけてプッシュした時点で、以下の処理をCI/CDで自動化させます。

  1. バージョンタグを取得する
  2. コンテンツをビルドする
  3. プレースホルダーに①を埋め込む
  4. 公開用のVPSサーバーへSSHで接続
  5. rsyncでコンテンツを同期する

と、こんな感じです。シェル一発でもできる作業なので、わざわざCI/CDで実現しなくても良さそうではありますが、実際やってみるとこれが結構便利で楽しかったです。なるほど、CI/CDというのはGitHub上にLinuxを立ち上げて、pushなどをトリガーにしてシェルを実行させるような仕組みなのですね! 仮想のmacOSなども実行できるので、iOSアプリのリリース作業もできそうです。ちょっと今まで使ってこなかったのが、損した気分になるほどCI/CDって便利かもです。

CI/CDとは

CI/CDは、ソフトウェアを「こまめに作って、こまめに届ける」ための自動化の仕組みで、まさに先に示した通りです。

CI(Continuous Integration)は、開発者がコードをpushするたびに、自動でビルド・テスト・静的解析を実行して、早い段階で不具合を見つけるしくみ。

CD(Continuous Delivery / Deployment)はテストを通った成果物を「いつでも本番に出せる状態」に自動で用意する(ステージング配置やアーティファクト化まで)。テスト通過後に「本番へ自動リリース」までやる。

こうすることで、早期にバグ検知、手作業ミス削減、リリース頻度向上、レビューと承認の見える化できます。

手順① サーバ側:デプロイ鍵を用意

ここからは実際に個人プロジェクトのデプロイをCI/CDで自動化させた手順をご紹介します。

ローカル(macOS)で鍵を作って、サーバーの authorized_keys に登録します。

# macOS 側で
ssh-keygen -t ed25519 -f ~/.ssh/nujs_deploy -C "nu-js deploy" -N ""

# cat 方式
cat ~/.ssh/nujs_deploy.pub | ssh xxx@example.com 'mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'

手順② GitHub Secrets を登録

リポジトリ Settings → Secrets and variables → Actions に以下を追加します。

注意点としては Environment secrets ではなく Repository secrets 側に設定するようにしてください。

手順③ ワークフローを作成(.github/workflows/deploy.yml)

ここまで準備ができましたら、いよいよ GitHub Actions を設定します。冒頭で説明した通り、大まかに次のような流れを実現してます。

  1. バージョンタグを取得する
  2. コンテンツをビルドする
  3. プレースホルダーに①を埋め込む
  4. 公開用のVPSサーバーへSSHで接続
  5. rsyncでコンテンツを同期する

タグ v* を push した時と、手動実行(workflow_dispatch)でこの Actions が走ります。バージョンはタグ名があれば優先して採用し、手動入力 or 日付でも埋めるようになってます。

name: deploy-nu-js

on:
  workflow_dispatch:
    inputs:
      version:
        description: 'VERSION(例: v2025.8.11.1)未入力なら実行時刻で生成'
        required: false
  push:
    tags:
      - 'v*'

jobs:
  buildAndDeploy:
    runs-on: ubuntu-latest
    env:
      SSH_HOST: ${{ secrets.SSH_HOST }}
      SSH_USER: ${{ secrets.SSH_USER }}
      SSH_PORT: ${{ secrets.SSH_PORT }}
      REMOTE_PATH: ${{ secrets.REMOTE_PATH }}

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

      - name: Decide version string
        id: ver
        shell: bash
        run: |
          if [ "${{ github.ref_type }}" = "tag" ] && [ -n "${{ github.ref_name }}" ]; then
            V="${{ github.ref_name }}"
          elif [ -n "${{ inputs.version }}" ]; then
            V="${{ inputs.version }}"
          else
            V="v$(date +%Y.%m.%d.%H%M)"
          fi
          echo "version=$V" >> "$GITHUB_OUTPUT"

      - name: Build (same as your script)
        shell: bash
        run: |
          set -e
          find ./ -name '.DS_Store' -delete -print || true
          echo "👉 build/ ディレクトリを再作成..."
          rm -rf build
          mkdir -p build/src build/static build/sample build/lib

          echo "📁 必要なファイルをコピー中..."
          cp index.html build/
          cp -R src build/
          cp -R static build/
          cp -R sample build/
          cp -R lib build/

          # バージョン埋め込み(Linux sed は -i 拡張子不要)
          sed -i "s/__VERSION__/${{ steps.ver.outputs.version }}/g" "build/index.html"

      # 入力の事前チェック(空なら明示的に fail)
      - name: Preflight check
        shell: bash
        run: |
          [ -n "${{ secrets.SSH_HOST }}" ] || { echo "❌ SSH_HOST is empty. Set repository secret or variable."; exit 1; }
          [ -n "${{ secrets.SSH_USER }}" ] || { echo "❌ SSH_USER is empty."; exit 1; }
          [ -n "${{ secrets.SSH_PRIVATE_KEY || '' }}" ] || { echo "❌ SSH_PRIVATE_KEY is empty."; exit 1; }
          [ -n "${{ secrets.REMOTE_PATH }}" ] || { echo "❌ REMOTE_PATH is empty."; exit 1; }

      - name: Setup SSH key
        shell: bash
        run: |
          set -e
          install -m 700 -d ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_deploy
          chmod 600 ~/.ssh/id_deploy
          # known_hosts 登録(ホスト鍵検証)
          ssh-keyscan -p "${SSH_PORT:-22}" "${SSH_HOST}" >> ~/.ssh/known_hosts

      - name: Prepare remote directory
        shell: bash
        run: |
          ssh -i ~/.ssh/id_deploy -p "${SSH_PORT:-22}" \
            -o StrictHostKeyChecking=yes \
            "${SSH_USER}@${SSH_HOST}" "mkdir -p '${REMOTE_PATH}'"

      - name: Deploy (rsync over SSH)
        shell: bash
        run: |
          rsync -avu --delete --exclude='.sass-cache' \
            -e "ssh -i ~/.ssh/id_deploy -p ${SSH_PORT:-22} -o StrictHostKeyChecking=yes" \
            build/ "${SSH_USER}@${SSH_HOST}:${REMOTE_PATH}"

      - name: Done
        run: echo "Done🎉 https://apppppp.com/kit/nu-js/"

使い方

完成した GitHub Actions が動くか試してみましょう。

タグのプッシュがトリガーで発火する設定なので、以下のように実行してみます。

git tag v2025.8.31.1 && git push origin v2025.8.31.1

公開サーバーへアクセスすると、最新が反映されてました。CI/CDがはじめての場合、結構感動しますね!

手動実行も可能です。Actionsdeploy-nu-jsRun workflow でプロンプトが表示されますので version を自由入力して実行できます。

Amazonで探す