GitHub Actions デプロイ戦略:VPSからクラウドプラットフォームへのCDパイプライン
はじめに
深夜3時、私は GitHub Actions のログ画面を凝視していました。赤いエラーが一行ずつ上へ流れていきます。「Host key verification failed」。またしても SSH の問題です。
これでデプロイ失敗は5回目。ローカルテストは全部通っているのに、GitHub にプッシュすると爆発する。その瞬間、思わず悪態をつきたくなりました。でも同時に気づいたのです。デプロイ戦略の選択は、思っていたよりずっと複雑だと。
自分で管理する VPS でも、Vercel や Cloudflare Pages のようなホスティングプラットフォームでも、どの方法にも落とし穴があります。選択を誤れば、深夜のデバッグ回数は増える一方です。
この記事では、GitHub Actions のいくつかのデプロイ戦略について語り、あなたに合った道を見つける手助けをします。
VPS SSH デプロイ:オールドスクールだが信頼できる
正直に言うと、最初は VPS デプロイに強い抵抗がありました。面倒くさいと思ったのです。SSH 鍵、known_hosts、rsync のパラメータ……やることが山ほどある、と。
でも何度か痛い目に遭ううちに、この「オールドスクール」なやり方こそ最も制御しやすいと気づきました。
SSH 鍵の設定:ハードコードしない
最もよくある悩みは、SSH 鍵をどこに置くかです。
初心者はよく秘密鍵を直接 workflow ファイルに書き込みます。これは大きな間違い。GitHub Secrets こそ正しい置き場所です。
リポジトリの Settings → Secrets → Actions で SSH_PRIVATE_KEY を追加します。そして workflow ではこう使います:
- name: Setup SSH
uses: webfactory/ssh-agent-action@v0.7.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
この action は自動で ssh-agent を起動し、鍵を読み込んでくれます。手間いらずです。
known_hosts:「Host key verification failed」を回避する
SSH が初めてサーバーへ接続するとき、この host を信頼するか尋ねてきます。対話式の問答は CI 環境では処理できないため、あらかじめサーバーのフィンガープリントを known_hosts に追加しておく必要があります。
方法は2つあります:
方法1:action で自動追加する
- name: Add server to known hosts
uses: webfactory/ssh-agent-action@v0.7.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
known-hosts: ${{ secrets.SSH_KNOWN_HOSTS }}
SSH_KNOWN_HOSTS の内容はこのように取得できます:
ssh-keyscan -H your-server.com >> known_hosts.txt
# ファイルの内容を GitHub Secrets にコピーする
方法2:手動で設定する
- name: Add server to known hosts
run: |
mkdir -p ~/.ssh
ssh-keyscan -H ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts
方法1のほうがクリーン、方法2は素早いデバッグ向きです。
rsync か scp か?
デプロイ時のファイル転送には、私は rsync を使います。理由はシンプルです:
- 変更のあったファイルだけを転送し、時間を節約できる
- 特定ディレクトリ(node_modules など)を除外できる
- 増分同期に対応している
典型的な rsync コマンドはこうです:
- name: Deploy to server
run: |
rsync -avz --delete \
--exclude 'node_modules' \
--exclude '.git' \
./dist/ ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}:/var/www/html/
--delete パラメータは、転送先にあって転送元に無いファイルを削除します。使うときは慎重に。設定を間違えると、消してはいけないものまで消えてしまいます。
デプロイ後のコマンド:サービスを再起動する
静的サイトなら転送が終われば完了です。でも Node.js アプリをデプロイする場合は、サービスの再起動も必要です。
私は Node プロセスの管理に PM2 を使うのが好きです。デプロイ後にこう実行します:
- name: Restart application
run: |
ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} \
"cd /var/www/app && pm2 restart all"
あるいは、より安全に特定のアプリだけ再起動する方法もあります:
- name: Restart application
run: |
ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} \
"pm2 restart my-app --update-env"
--update-env は環境変数を再読み込みするので、設定に変更があった場合に向いています。
クラウドプラットフォームデプロイ:ホスティングサービスの手軽さ
VPS デプロイの問題は、サーバーを自分で管理しなければならないことです。セキュリティパッチ、SSL 証明書の更新、ファイアウォールのルール……雑務が山積みです。
ホスティングプラットフォームならずっと楽です。コードをプッシュすれば、自動でビルドして自動でデプロイ。あなたはコードを書くことだけに集中できます。
Vercel:フロントエンドプロジェクトの第一選択
Vercel のフロントエンドプロジェクトへの対応は、ほぼ完璧です。Next.js、Astro、React——ワンクリックでデプロイ、設定ゼロ。
ただしプロジェクトにバックエンド API が必要な場合は注意が必要です。Vercel の Serverless Functions には実行時間の制限があります(無料版10秒、Pro 版60秒)。超過すると timeout します。
純粋な静的サイトやシンプルな API なら、Vercel で十分です。複雑なバックエンドサービスはやはり自前で用意するしかありません。
GitHub Actions で Vercel にデプロイする設定はこうです:
name: Deploy to Vercel
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Vercel CLI
run: npm i -g vercel@latest
- name: Pull Vercel Environment Information
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
- name: Build Project Artifacts
run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
- name: Deploy Project Artifacts to Vercel
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
VERCEL_TOKEN は Vercel のコンソールで生成し、GitHub Secrets に保存します。
Cloudflare Pages:無料枠が寛大
Cloudflare Pages の無料枠は Vercel よりずっと寛大です。帯域は無制限、ビルド回数は月500回——個人プロジェクトには余裕で足ります。
しかも Cloudflare のグローバル CDN は本当に速い。私自身で計測しましたが、アジアからのアクセス速度は Vercel より安定していました。
デプロイ設定はこうです:
name: Deploy to Cloudflare Pages
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: npm run build
- name: Deploy
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: my-project
directory: dist
Cloudflare にはもう一つ利点があります。R2 ストレージの無料枠も大きいのです。静的アセットを R2 に置き、Pages の CDN と組み合わせれば、読み込み速度をかなり上げられます。
Netlify:老舗の安定派
Netlify は前の2つほど使っていませんが、老舗のホスティングプラットフォームでエコシステムも成熟しています。
デプロイ設定も似たようなものです:
- name: Deploy to Netlify
uses: netlify/actions/cli@master
with:
args: deploy --prod
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
Netlify の Form handling 機能はなかなか実用的です。フォーム送信を自動で処理してくれるので、シンプルなマーケティングページに向いています。
ホスティングプラットフォームの制約
とはいえ、ホスティングプラットフォームも万能ではありません。
よくある制約をいくつか挙げます:
- ビルド環境の制限:メモリも CPU も上限があり、大規模プロジェクトはビルドに失敗することがある
- カスタマイズ性の低さ:nginx 設定を変えたい?できません
- プラットフォーム依存:プラットフォームが倒産したり方針を変えたりすれば、移行を迫られる
- 国内アクセスの問題:一部のプラットフォームは国内からのアクセスが不安定(Cloudflare は改善されているが)
プロジェクトに完全な制御が必要なら、やはり VPS に戻るしかありません。
ハイブリッド戦略:柔軟性と制御を両立する
多くのプロジェクトは「純粋な静的」でも「純粋なバックエンド」でもありません。フロントエンドは Next.js、バックエンドはデータベースに接続して定期タスクも走らせる……。
そんなときは、ハイブリッドデプロイが最適解になり得ます。
静的ページはホスティング + API は VPS にデプロイ
典型的な構成はこうです:
- 静的ページ(HTML/CSS/JS)は Cloudflare Pages か Vercel にデプロイ
- Node.js の API サービスは自分の VPS にデプロイ
- データベースも VPS 上に置く(または Supabase/PlanetScale でホスティング)
メリットはそれぞれの強みを活かせること:
- フロントエンドは CDN 高速化と自動 HTTPS の恩恵を受けられる
- バックエンドは完全な制御権を持ち、ホスティングプラットフォームの制約を受けない
- データベースアクセスの遅延が低い(API とデータベースが同じマシン上にある)
GitHub Actions のマルチステージデプロイ
1つの workflow で両方の場所へ同時にデプロイします:
name: Hybrid Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
outputs:
artifact-path: ./dist
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist
deploy-frontend:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: build-output
path: dist
- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: my-frontend
directory: dist
deploy-backend:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup SSH
uses: webfactory/ssh-agent-action@v0.7.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Deploy API to VPS
run: |
rsync -avz --delete \
--exclude 'node_modules' \
./api/ ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}:/var/www/api/
- name: Restart API service
run: |
ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} \
"cd /var/www/api && npm install && pm2 restart api"
この workflow には3つの job があります:
build:プロジェクトをビルドし、静的ファイルを生成するdeploy-frontend:静的ファイルを Cloudflare Pages にデプロイするdeploy-backend:API を VPS にデプロイしてサービスを再起動する
needs: build によって、デプロイ job はビルド完了後にのみ実行されます。upload-artifact と download-artifact が job 間でビルド成果物を受け渡します。
環境変数の分離
ハイブリッドデプロイの課題の一つは、フロントエンドとバックエンドで環境変数が異なることです。
フロントエンドは API のアドレスを知る必要があり、バックエンドはデータベースのパスワードを知る必要があります。
私のやり方はこうです:
# フロントエンド job
- name: Set frontend env
run: |
echo "API_URL=https://api.mydomain.com" >> $GITHUB_ENV
# バックエンド job
- name: Deploy with env
run: |
ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} \
"cd /var/www/api && pm2 restart api --update-env DATABASE_URL=${{ secrets.DATABASE_URL }}"
機密情報(データベースのパスワード、API トークン)は必ず GitHub Secrets を通します。非機密情報(API のアドレス)は workflow に書いて構いません。
実戦設定の例
以下は、これまでに触れたポイントをすべて網羅した、完全な VPS デプロイ workflow です。
完全な workflow ファイル
name: Deploy to VPS
on:
push:
branches: [main]
workflow_dispatch: # 手動でデプロイを発火
env:
NODE_VERSION: '20'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: dist
path: dist
retention-days: 1
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Setup SSH
uses: webfactory/ssh-agent-action@v0.7.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Add server to known hosts
run: |
mkdir -p ~/.ssh
ssh-keyscan -H ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts
- name: Deploy files
run: |
rsync -avz --delete \
--exclude '.htaccess' \
./dist/ ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}:${{ secrets.DEPLOY_PATH }}
- name: Verify deployment
run: |
ssh ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} \
"ls -la ${{ secrets.DEPLOY_PATH }}"
- name: Send deployment notification
if: always()
run: |
curl -X POST "${{ secrets.NOTIFICATION_WEBHOOK }}" \
-H "Content-Type: application/json" \
-d '{"text": "Deployment completed: ${GITHUB_SHA}"}'
設定が必要な Secrets
| Secret 名 | 説明 | 取得方法 |
|---|---|---|
SSH_PRIVATE_KEY | SSH 秘密鍵の内容 | ローカルで生成、公開鍵はサーバーに置く |
SERVER_HOST | サーバーの IP またはドメイン | あなたの VPS の情報 |
SERVER_USER | SSH ログインのユーザー名 | 通常は root か ubuntu |
DEPLOY_PATH | デプロイ先のパス | 例:/var/www/html |
NOTIFICATION_WEBHOOK | デプロイ通知の送信先 | Slack/Telegram の webhook |
頻出トラブルの調査
デプロイに失敗したとき、ログを見ても情報が多すぎて目が回りがちです。
私の調査順序はこうです:
- SSH 接続の問題:「Setup SSH」と「Add server to known hosts」のステップを見る
- 失敗していたら、鍵の形式と known_hosts の内容を確認
- rsync 転送の問題:「Deploy files」のステップを見る
- 失敗していたら、パスの存在と権限の正しさを確認
- サービス再起動の問題:「Verify deployment」のステップを見る
- 失敗していたら、デプロイ先にファイルがあるかを確認
ちょっとしたコツ:失敗したステップの後ろにデバッグ出力を追加します。
- name: Debug SSH connection
if: failure()
run: |
echo "SSH config:"
cat ~/.ssh/config || echo "No config file"
echo "Known hosts:"
cat ~/.ssh/known_hosts || echo "No known_hosts file"
ssh -v ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} echo "Connection test"
ssh -v は詳細なログを出力するので、問題がどこにあるか見えてきます。
まとめ
これだけ書いてきましたが、要するに一言です。完璧なデプロイ方法は存在せず、あるのはあなたのプロジェクトに最も合った方法だけ。
選定のアドバイス:
- 純粋な静的サイト(ブログ、ドキュメント):Cloudflare Pages か Vercel で手間いらず
- シンプルな API + フロントエンド:ホスティングプラットフォームで十分、VPS で苦労しない
- 複雑なバックエンド + データベース:VPS かクラウドサーバー、制御権が重要
- ハイブリッド構成:フロントエンドはホスティング + バックエンドは VPS、それぞれの強みを活かす
どれを選んでも、GitHub Actions の設定パターンはほぼ同じです。ビルド → 転送 → 再起動。この3ステップを切り分けて整理しておけば、デバッグ時に方向を見失いません。
もう一つ。デプロイに失敗しても慌てないこと。ログを段階別に見て、まず SSH 接続の問題かコマンド実行の問題かを特定しましょう。デバッグステップを一つ加えれば、問題はすぐに姿を現します。
次に深夜3時のデプロイ失敗に遭ったとき、あなたがもっと早く原因にたどり着けますように。
GitHub Actions による VPS デプロイの設定
GitHub Actions から SSH 経由で VPS にデプロイするまでの完全な手順
⏱️ 目安時間: 30 分
- 1
ステップ1: SSH 鍵ペアを生成する
ローカルでデプロイ専用の SSH 鍵を生成します:
• ssh-keygen -t ed25519 -C "deploy@github" -f deploy_key
• 公開鍵(deploy_key.pub)をサーバーの ~/.ssh/authorized_keys に追加
• 秘密鍵(deploy_key)の内容を GitHub Secrets の SSH_PRIVATE_KEY に保存 - 2
ステップ2: GitHub Secrets を設定する
リポジトリの Settings → Secrets → Actions で追加します:
• SSH_PRIVATE_KEY:秘密鍵の全内容
• SERVER_HOST:サーバーの IP またはドメイン
• SERVER_USER:SSH ユーザー名(root や ubuntu など)
• DEPLOY_PATH:デプロイ先のパス - 3
ステップ3: Workflow ファイルを作成する
.github/workflows/deploy.yml にデプロイ設定を作成します:
• SSH 鍵設定ステップを追加(webfactory/ssh-agent-action)
• known_hosts を設定して host verification の失敗を防ぐ
• rsync でビルド成果物を転送
• デプロイ後にサービス再起動コマンドを実行 - 4
ステップ4: デプロイフローをテストする
コードをプッシュして自動デプロイを発火させるか、手動で実行します:
• 各ステップのログ出力を確認
• SSH 失敗時は鍵の形式と known_hosts を確認
• rsync 失敗時はパスと権限を確認
• デバッグステップを追加して問題を切り分け
FAQ
GitHub Actions のデプロイで「Host key verification failed」が出たらどう解決しますか?
• 方法1:ssh-keyscan でサーバーのフィンガープリントを取得し、SSH_KNOWN_HOSTS という Secret に保存する
• 方法2:workflow 内で手動で ssh-keyscan -H $SERVER_IP >> ~/.ssh/known_hosts を実行する
よりクリーンで安全な方法1を推奨します。
SSH 鍵はどこに置くべきですか?workflow ファイルに直接書いても大丈夫ですか?
Vercel、Cloudflare Pages、Netlify のうち個人プロジェクトにはどれが向いていますか?
純粋な静的サイトなら Cloudflare Pages を優先しましょう。
ハイブリッドデプロイ構成にはどんなメリットがありますか?
GitHub Actions のマルチ job workflow を使えば、両方へ同時にデプロイできます。
デプロイ失敗時にログが多すぎて読み切れません。素早く問題を特定するには?
1. SSH 接続の問題 → Setup SSH と known_hosts のステップを確認
2. rsync 転送の問題 → パスの存在と権限を確認
3. サービス再起動の問題 → デプロイ先のファイル一覧を確認
失敗したステップの直後にデバッグ出力(ssh -v の詳細ログ)を追加すると、問題がすぐに見えてきます。
rsync の --delete パラメータにはどんなリスクがありますか?
初回デプロイでは --delete を付けず、正しいことを確認してから有効化するのがおすすめです。あるいは --delete-excluded で除外対象のファイルだけを削除する手もあります。
4分で読めます · 公開日: 2026年4月7日 · 更新日: 2026年6月8日
関連記事
セルフホスト Dev Sandbox:Docker と Go でプレビュー環境を作る
セルフホスト Dev Sandbox:Docker と Go でプレビュー環境を作る
Cloudflare Pro か Business か?3 つの軸で判断するアップグレード判断ツリー
Cloudflare Pro か Business か?3 つの軸で判断するアップグレード判断ツリー
社内ネットワーク Docker pull タイムアウトのトラブルシューティング:DNS・プロキシ・ミラー加速の完全ガイド
コメント
GitHubアカウントでログインしてコメントできます