🚀

【個人開発】Kamalで始めるVPSデプロイ ─ 月700円でNext.js(SSR)を運用する

に公開

要点まとめ

  • Next.js(SSR)+ Kamal + VPSで月額約6〜700円の固定費インフラを構築できる(VPS 約600円 + ドメイン約100円)。KamalはDockerベースのアプリケーションをVPSに簡単にデプロイするツールで、リバースプロキシやSSL証明書の管理も自動化してくれる

  • 基本的なセキュリティ設定も実装: SSH公開鍵認証、rootログイン無効化、パスワード認証無効化など、本番環境で必要な基本的なセキュリティ設定を実装できる

  • 学習価値と汎用性の両立: Docker、サーバー管理、デプロイの仕組みを理解しながら、Next.js以外(Railsなど)にも横展開できる

  • 固定費で安心して使える: クラウド(AWS/GCP)のような従量課金がなく、予期しない請求を心配する必要がない

はじめに

個人開発でアプリを公開する際、Vercelは非常に手軽ですが、インフラの理解があまり進みません。一方で、AWS/GCPは強力ですが、料金体系が複雑で従量課金による予期しない請求が不安です。

本記事では、個人開発プロジェクト「TechSeek」での実践例を通じて得た知見を基に、Next.js(SSR)+ Kamal + VPS を使った、月額約6〜700円の固定費で「自分で理解しながら運用できる」インフラ構築手順を紹介します。

この記事を読み終えると、月額約6~700円の固定費インフラを理解しながら構築でき、GitHubにpushするだけで自動デプロイされ、HTTPSでアクセスできるNext.jsアプリが運用できるようになります。

TechSeekについて

TechSeekは、技術書の検索機能を提供するWebアプリケーションです。現在約100冊の技術書データを扱っており、今後さらにデータを増やしていく予定です。

TechSeekの検索画面

  • 技術スタック:
    • フロントエンド: Next.js 15(BFF・SSR), React 19, TypeScript, vanilla-extract, SWR, Zustand, aspida
    • バックエンド: Rails 8 API, SQLite, ActiveAdmin, Sidekiq, rswag
    • 外部API: OpenAI API, 楽天Books API
    • インフラ: ConoHa VPS, Kamal(Dockerデプロイ), Cloudflare CDN
    • テスト: RSpec, Vitest, React Testing Library, MSW
    • 開発環境: Storybook, ESLint, Prettier, RuboCop, Ruby LSP

TechSeekでは、まさに本記事で紹介するKamal + VPSの構成を使って、Next.jsアプリケーションをデプロイしています。GitHub Actionsから自動デプロイが実行され、HTTPSでアクセスできる状態で運用しています。

対象読者

以下のいずれかに当てはまる方におすすめです。

  • Vercel Hobby を卒業し、Proプラン($20/月)への移行を迷っているが、費用は抑えたい

  • AWS/GCP の従量課金が怖く、固定費で運用したい

  • インフラの理解を深めながら Docker やデプロイの基礎を身につけたい

前提知識

  • Next.jsアプリがローカルで動作している

  • GitHubアカウントを持っている

  • 基本的な git と ssh が使える

Kamalとは

DockerベースのアプリケーションをVPSに簡単にデプロイするためのツールです。Rails向けに開発されましたが、Dockerコンテナを扱えるあらゆるアプリケーション(Next.js、Railsなど)に使用できます。

Kamalの主な特徴:

  • シンプルな設定: config/deploy.yml という1つの設定ファイルでデプロイ先や環境変数などを管理
  • Dockerベース: アプリケーションをDockerコンテナとしてビルド・デプロイ
  • 自動リバースプロキシ: kamal-proxyを自動でセットアップし、HTTPS対応のリバースプロキシを提供
  • SSL証明書の自動取得: Let's Encryptを使ってHTTPS証明書を自動で取得・更新
  • SSH経由のデプロイ: VPSにSSH接続して、コンテナの起動・停止・更新する
  • ゼロダウンタイムデプロイ: 新しいコンテナを起動してから古いコンテナを停止することで、サービスを停止せずにデプロイ可能

従来、VPSにアプリケーションをデプロイするには、Docker Composeの設定、リバースプロキシ(NginxやTraefik)の設定、SSL証明書の取得・更新など、複数の手順が必要でした。Kamalはこれらの作業を1つのコマンド(kamal deploy)で自動化し、kamal-proxyがリバースプロキシとSSL証明書の管理を担当してくれます。

なぜ Kamal + VPS を選んだか

個人開発でインフラを選ぶ際、次の3つを重視しました:

  1. コストが予測できること(固定費)
  2. インフラの学習価値があること
  3. 他フレームワークにも応用できること(汎用性)

それぞれの選択肢を比較すると次の通りです。

選択肢 月額コストの目安 メリット デメリット
Vercel 無料〜$20/月 使いやすさ: Next.js 向けに最適化されており、GitHub連携だけでデプロイできる
無料で始められる: Hobbyプランなら完全無料(ただしビルド時間や帯域に制限あり)
マネージド運用: SSL・CDN・スケールなどをほぼ意識せずに使える
インフラの学びが少ない: OS・Docker・ネットワークなどを触る機会が少ない
フレームワーク依存性: Next.js 前提で他スタックへ横展開しづらい
AWS/GCP 従量課金(利用量に依存) 汎用性・拡張性: 小規模〜大規模まで幅広い構成を取れる
マネージドサービスが豊富: DB・監視・ログ・キューなどは「設定は必要だが、その後の運用はクラウド側が自動管理」。バックアップ・障害検知・アップデートなど、通常は自分で行う作業を任せられる
コスト管理機能: 予算アラートや上限設定が利用できる
料金体系が複雑: CPU/メモリ/転送料/I/Oなど課金要素が多く、請求の予測が難しい
初学者にはオーバースペック: 学習コストと設計負荷が高い
設定ミスで高額請求リスク: 適切に制御しないと従量課金が跳ね上がる可能性がある
Kamal + VPS 約6〜700円/月(固定費) 学習価値: Docker・SSH・DNS・OS設定など、Webアプリ運用の基礎が一通り身につく
汎用性: Next.js だけでなく Rails や他のフレームワークも同じ構成でデプロイ可能
コストが読みやすい: 毎月の固定費のみで、従量課金の心配がない
初期構築と運用の手間: セキュリティ設定、バックアップ、ログ管理などを自分で用意する必要がある
マネージド機能がない: オートスケールやマネージドDBは提供されないため、必要なら別途ツールを導入する
単一VPS構成のリスク: 障害時の復旧手順も自分で整備が必要

この比較から、個人開発でインフラを理解しながら運用したい人にとって「Kamal + VPS」は、Vercelの学習機会の少なさやAWS/GCPの複雑さ・従量課金リスクを避けつつ、固定費の安心感と汎用性を両立できる選択肢だと考えました。

本記事のゴールとなるインフラ構成

デプロイフロー:

  1. 開発者がGitHubにコードをプッシュ
  2. GitHub Actionsが自動実行
  3. コンテナイメージをビルドしてGitHub Container Registryにプッシュ
  4. Kamalを使ってVPSにデプロイ
  5. VPS上でDockerがGitHub Container Registryからコンテナイメージをプル
  6. ユーザーがHTTPSアクセス(kamal-proxyがLet's Encryptから自動取得したSSL証明書を使用)

本記事で進めるインフラ構築手順の全体像

確認項目
1. ドメイン取得・VPS契約・DNS設定 ssh conohaでSSH接続できる
dig <YOUR_DOMAIN> +shortでVPSのIPアドレスが表示される
2. セキュリティ設定(SSH / OS) • 管理者ユーザーで公開鍵認証のみになっている
• rootログインとパスワード認証が無効化されている
3. Kamalインストール・設定 • ローカル環境からkamal versionでバージョンが表示される
• ローカル環境からkamal server exec "date"でVPS接続が確認できる
• ローカル環境からkamal registry loginでレジストリ接続が確認できる
4. Next.jsアプリケーション準備 docker run -p 3000:3000 ghcr.io/YOUR_USERNAME/YOUR_APP_NAME:latestでローカルで動作する
docker pull ghcr.io/YOUR_USERNAME/YOUR_APP_NAME:latestでGitHub Container Registryからイメージが取得できる
config/deploy.ymlのイメージ名が設定されている
5. Kamalデプロイ実行 kamal app detailsでデプロイ状態が確認できる
curl -I https://<YOUR_DOMAIN>でHTTPSレスポンスが返ってくる
• ブラウザでhttps://<YOUR_DOMAIN>にアクセスしてアプリケーションが表示される
6. GitHub Actions自動化 • GitHubリポジトリの「Actions」タブでワークフローが正常に完了する
• コードをプッシュすると自動的にデプロイが実行される
curl -I https://<YOUR_DOMAIN>で最新のデプロイが反映されている

1. ドメイン取得・VPS契約・DNS設定

この章では、Kamalでデプロイする前に実施する準備として、ドメイン取得、VPS契約、DNS設定の手順を紹介します。

これらの設定により、独自ドメインでアプリケーションにアクセスできるようになります。

1章で進めること

  • ドメインを取得し、WHOIS代理公開をONにする
  • VPSを契約し、SSH鍵とconfigを整備する
  • ドメインとVPSをDNSで紐付け、疎通を確認する

1-1. ドメインを取得する(ムームードメイン/お名前.com)

取得手続きは公式ドキュメントを参考に進めます。

ここでは、実際に取得する際の注意点をまとめます。

1. ドメインは「初年度の取得費用」と「2年目以降の更新費用」が分かれている

  • 初年度だけ極端に安くても、更新は年間数千円かかるケースがほとんどです。
  • 個人開発であれば、更新前に解約して様子を見る選択肢もあります。

多くのレジストラでは、解約しても契約終了日までは利用できます。ただし、仕様は事業者ごとに異なるため、利用中のレジストラの解約ポリシーを確認することをおすすめします。

2. WHOIS代理公開は必ず ON

  • WHOIS とは「取得したドメインの所有者情報(氏名・住所など)を全世界に公開する仕組み」です。

  • 代理公開を OFF にすると、自宅住所や氏名がそのまま公開されるため危険です。

  • そのため、ムームードメイン / お名前.com には「所有者本人の情報の代わりに、レジストラ(例:GMO など)の情報を公開する機能」があります。

  • 代理公開を ON に設定した後、 https://www.whois.com/ (公開された WHOIS 情報を誰でも検索できるサイト) で取得したドメイン名を検索します。検索後に表示されるページ上の Registrar Information → Registrar に 自分の氏名ではなくレジストラ名(例:GMO) が表示されていることを確認する。

1-2. VPS サービスのアカウントを作成し契約する(ConoHa/さくらのVPS)

作成・契約手続きは、公式ドキュメントを参考に進めます。

ここでは「どのOS・プランを選べばよいか」の判断指針をまとめます。

OSの選び方

Ubuntu LTSがおすすめ

Ubuntuは広く利用されているため、Kamalやその他のデプロイツールに関する情報やトラブルシューティングの情報が豊富です。

プランの選び方

個人開発でWebサイトを立ち上げる場合、以下のスペックのプランが目安になります:

  • メモリ: 2GB
  • CPU: 3コア
  • SSD: 100GB

多くのVPSプロバイダーでは「Webサイトの立ち上げに安心のスペック」として推奨されていることが多いです。トラフィックが増加した場合は、後からスケールアップできます。

1-3. SSH 接続の準備をする(鍵作成・config 設定)

ConoHa上でSSH鍵を生成してローカルに配置する

  1. ConoHaのコントロールパネルでSSH鍵を生成し、秘密鍵をダウンロードします
  2. ダウンロードした秘密鍵を ~/.ssh/ に移動し、権限を設定します:
mv ~/Downloads/conoha_key.pem ~/.ssh/
chmod 600 ~/.ssh/conoha_key.pem

SSH接続の確認

IPアドレスでSSH接続できることを確認します:

ssh -i ~/.ssh/conoha_key.pem root@123.45.67.89

SSH config の設定

SSH接続の際、システムのSSH設定(~/.ssh/config)を参照します。

SSH configを設定しておくと、Kamalの設定ファイルでホスト名を使えるようになります。

~/.ssh/config ファイルを編集して、以下のように設定します:

vim ~/.ssh/config
Host conoha
    HostName 123.45.67.89
    User root
    Port 22
    IdentityFile ~/.ssh/conoha_key.pem

設定後、エイリアス名で接続できることを確認します:

ssh conoha

1-4. DNS を設定してドメインと VPS を紐付け、DNS が反映されたことを確認する(dig / curl で疎通確認)

DNS設定とは

DNS設定とは、ドメイン名(例: <YOUR_DOMAIN>)をVPSのIPアドレスに紐付けることです。

注意: 本記事では、<YOUR_DOMAIN>は取得した独自ドメイン(例: example.com)を表します。以降、この表記が出てきたら自分の環境に合わせて読み替えてください。

現在の状態:

  • ドメイン: <YOUR_DOMAIN> を取得済み
  • VPS: IPアドレス(例: 123.45.67.89)が発行済み
  • 一方で、<YOUR_DOMAIN> にアクセスしても、まだVPSに接続されない

DNS設定により:

  • <YOUR_DOMAIN> にアクセスすると、自動的にVPSのIPアドレスに接続されるようになります
  • ブラウザで http://<YOUR_DOMAIN> を開くと、VPS上で動作しているWebアプリケーションが表示されます

DNS設定手順(ConoHa VPS + ムームードメインの場合)

DNS設定では、ネームサーバーでAレコードを設定し、ドメインとVPSのIPアドレスを紐付けます。

ConoHa VPSを利用する場合は、ConoHaが提供するDNSサーバーを利用して設定するのがおすすめです。

  1. ConoHaのコントロールパネルでDNS設定を実施する

    • ConoHaのコントロールパネルにログイン
    • 「DNS」→「ドメイン」で取得したドメインを登録
    • Aレコードを追加(ルートドメイン @ または空欄 → VPSのIPアドレス)
    • CNAMEレコードを追加(www<YOUR_DOMAIN>
    • ConoHaのネームサーバー情報(例: ns1.conoha.ne.jp)を確認
    • 詳細な手順: ConoHa VPS - DNSを使う
  2. ムームードメインでネームサーバーを変更する

    • ムームードメインのコントロールパネルにログイン
    • 「ドメイン操作」→「ネームサーバー設定変更」を選択
    • 「GMOペパボ以外のネームサーバー」を選択
    • ConoHaのネームサーバー(上記で確認したもの)を設定
    • ムームードメインのコントロールパネルから「ドメイン操作」→「ネームサーバー設定」で実施できます

注意: ネームサーバーの変更が反映されるまで、2〜3日かかる場合があります。

DNS設定の確認

DNS設定が正しく反映されているか確認するため、digコマンドを使用してドメインのIPアドレスを確認します。

dig <YOUR_DOMAIN> +short

期待される出力例:

123.45.67.89

補足: 複数のAレコードが設定されている場合や、CNAMEレコードが設定されている場合は、複数行で表示されることがあります。この場合でも、VPSのIPアドレス(例: 123.45.67.89)が含まれていれば、DNS設定は正しく反映されています。

参考:

DNS設定とは何か?

なぜDNS設定が必要なのか?

ブラウザで <YOUR_DOMAIN> にアクセスする際、実際にはVPSのIPアドレス(例: 123.45.67.89)に接続します。

ユーザーはIPアドレスを覚える必要はありません。DNS設定により、<YOUR_DOMAIN> を入力すると自動的にIPアドレスに変換されます。

1. ドメイン取得の仕組みと役割

ドメインを取得すると、レジストラ(ムームードメイン)があなたのドメインの所有者情報を管理します。

ドメイン取得直後(ムームードメインDNSを使用)

2. ドメインとVPSの“つなぎこみ”をイメージで理解する

3. ネームサーバー切替の全体像と流れを図解

Aレコードを設定しても、まだレジストリのTLDサーバーには「権威DNSサーバーはムームードメインDNS」という情報が登録されたままです。

設定完了後のDNS解決の流れ

ユーザーが <YOUR_DOMAIN> にアクセスした時のDNS解決の流れは以下の通りです。

まとめ:

  1. ドメイン取得: レジストリに権威DNSサーバー(ムームードメインDNS)の情報が登録される
  2. Aレコード設定: ConoHa DNSにドメイン名とIPアドレスの対応関係を設定
  3. ネームサーバー変更: レジストリの権威DNSサーバー情報をConoHa DNSに変更
  4. DNS解決: レジストリ → ConoHa DNS → IPアドレス → VPS という流れで接続できるようになる

これにより、<YOUR_DOMAIN> にアクセスすると、自動的にVPSのIPアドレス(123.45.67.89)に接続できるようになります。

1章の完了確認

  • ✅ ドメインを取得し、WHOIS代理公開をONにする(1-1)
  • ✅ VPSを契約し、SSH鍵とconfigを整備する(1-2、1-3)
  • ✅ ドメインとVPSをDNSで紐付け、疎通を確認する(1-4)

2. セキュリティ設定(VPS / SSH / OS)

この章では、VPSのセキュリティを強化します。rootログインやパスワード認証を無効化し、その結果として公開鍵認証のみで安全に接続できる状態にします。

注意: 新ユーザーでSSH接続できることを確認するまでは、rootログインを無効化しないことをおすすめします。誤設定で接続できなくなるリスクがあるため、手順ごとに確認しながら進めることをおすすめします。

2章で進めること

  1. 2-1: rootログインとパスワード認証を無効化する
  2. 2-2: 公開鍵認証のみを有効化

2-1. root ログインとパスワード認証を無効化する

管理者ユーザーの作成

# ホストマシンからVPSにrootでSSH接続
# 注意: YOUR_SERVER_IPはVPSのグローバルIPアドレス(例: 123.45.67.89)を表します
ssh root@YOUR_SERVER_IP

# VPS上で新しいユーザーを作成(例: deploy)
adduser deploy

# VPS上で管理者権限(sudo)を付与
usermod -aG sudo deploy

# VPS上で作成したユーザーでログインできることを確認
su - deploy
sudo whoami
# 期待される結果: root

公開鍵認証の設定

# VPS上でrootユーザーに戻る
exit

# VPS上で新ユーザーのSSHディレクトリを作成
mkdir -p /home/deploy/.ssh

# VPS上で既存のrootの公開鍵をコピー(初期セットアップ時はこれでOK)
cp /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys

# VPS上で権限を設定(重要:権限が正しくないとSSH接続できない)
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

新ユーザーでの接続テスト

# ホストマシンから新ユーザーでSSH接続テスト
ssh deploy@YOUR_SERVER_IP

# VPS上で接続できたら、sudo権限を確認
sudo whoami
# 期待される結果: root

SSH設定の変更

# VPS上でSSH設定ファイルを編集
sudo nano /etc/ssh/sshd_config

以下の設定を変更します:

# rootログインを無効化
PermitRootLogin no

# パスワード認証を無効化
PasswordAuthentication no

# 公開鍵認証のみ許可
PubkeyAuthentication yes

SSH設定の適用

# VPS上で設定ファイルの構文チェック
sudo sshd -t

# VPS上で構文エラーがないことを確認してから、SSH設定を再読み込み
sudo systemctl reload ssh

# VPS上でSSHサービスの状態を確認
sudo systemctl status ssh

# VPS上で実際に反映されている設定を確認(設定ファイルを直接見るのではなく、sshdが読み込んでいる設定を表示)
sudo sshd -T | grep -E "permitrootlogin|passwordauthentication|pubkeyauthentication"
# 期待される結果:
# permitrootlogin no
# passwordauthentication no
# pubkeyauthentication yes

config/deploy.ymlの更新

# config/deploy.yml
servers:
  web:
    hosts:
      - YOUR_SERVER_IP
    ssh:
      user: deploy  # rootから変更

設定を確認:

# ホストマシンで実行
kamal config

SSH configの更新

# ホストマシンで実行
vim ~/.ssh/config
Host conoha
    HostName 123.45.67.89
    User deploy  # rootから変更
    Port 22
    IdentityFile ~/.ssh/conoha_key.pem

2-2. 公開鍵認証のみを有効化

パスワード認証を無効化し、その結果として公開鍵認証のみで接続できることを確認します。

公開鍵認証の確認

# ホストマシンから公開鍵認証で接続
ssh deploy@YOUR_SERVER_IP

# パスワード認証が無効になっていることを確認
# (パスワードを求められないことを確認)

2章の完了確認

  • 管理者ユーザーを作成し、rootログインを無効化(2-1)
  • 公開鍵認証に一本化(2-2)

3. Kamalインストール・設定

この章では、Kamalのインストールと設定ファイル(config/deploy.yml)の作成を進めます。

Kamalの設定ファイルは、VPSへのデプロイ方法を定義する重要なファイルです。この章を完了すると、ローカル環境からVPSに接続でき、デプロイの準備が整います。

Kamalのデプロイ手順

この章では、ローカル環境からKamalコマンドを実行してVPSにアプリケーションをデプロイする手順を紹介します。以下の図は、この章で進める手動デプロイの流れを示しています。

デプロイフロー:

① 開発者がKamalコマンドを実行: ローカル環境でkamal deployコマンドを実行
② レジストリからイメージ情報を取得: KamalがGitHub Container Registryからコンテナイメージの情報を取得
③ VPSにSSH接続してデプロイ: KamalがVPSにSSH接続し、その結果としてデプロイを実行
④ VPS上でDockerがイメージをプル: VPS上でDockerがGitHub Container Registryからコンテナイメージをプルし、コンテナを起動

3章で進めること

  • まず「Kamalのデプロイ手順」の流れ(上図の①〜④)を前提として押さえる
  • 3-1: ローカル環境にKamalをインストールする
  • 3-2: config/deploy.ymlを作成する
  • 3-3: VPSのIPアドレス(またはSSHエイリアス)を設定し、接続を確認する
  • 3-4: レジストリ設定とシークレットを用意し、接続を確認する

3-1. ローカルに Kamal をインストールする

KamalはRuby gemとして提供されているため、Ruby環境(Ruby 3.0以上)をインストールします。

インストール手順は公式ドキュメントを参考に進めます。ここでは、インストール前の確認事項とインストール手順をまとめます。

Kamalインストール前の確認

Kamalをインストールする前に、以下を確認します。

  • Ruby環境: Ruby 3.0以上が推奨
  • Docker Desktop: ローカル環境でDockerが動作すること
  • SSH接続: VPSへのSSH接続ができること

Ruby環境の確認

ruby --version

期待される出力例: ruby 3.2.0 (またはそれ以上)

Docker Desktopの確認

docker --version
docker ps

Kamal gemのインストール

  1. Ruby gemとしてインストールします。
gem install kamal
  1. インストール確認をします。
kamal version

インストールが成功すると、Kamalのバージョンが表示されます(例: kamal 2.0.0)。

参考

3-2. Kamal の設定ファイル(config/deploy.yml)を作成する

Kamalの設定ファイルは、VPSへのデプロイ方法を定義する重要なファイルです。kamal initコマンドで初期化し、その後プロジェクトに合わせてカスタマイズします。

プロジェクトの初期化

Next.jsプロジェクトのルートディレクトリで、Kamalを初期化します。

kamal init

このコマンドで以下のファイルが生成されます。

  • config/deploy.yml - デプロイ設定ファイル(Kamalのメイン設定ファイル)
  • .kamal/secrets - 機密情報を保存するファイル(.gitignoreに追加される)

設定ファイルの基本構造

生成されたconfig/deploy.ymlを確認します。

cat config/deploy.yml

初期状態では、以下のようなテンプレートが生成されます。

config/deploy.yml のテンプレート
# Name of your application. Used to uniquely configure containers.
service: my-app

# Name of the container image.
image: my-user/my-app

# Deploy to these servers.
servers:
  web:
    - 192.168.0.1
  # job:
  #   hosts:
  #     - 192.168.0.1
  #   cmd: bin/jobs

# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.
# Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer.
#
# Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption.
proxy:
  ssl: true
  host: app.example.com
  # Proxy connects to your container on port 80 by default.
  # app_port: 3000

# Credentials for your image host.
registry:
  server: localhost:5555
  # Specify the registry server, if you're not using Docker Hub
  # server: registry.digitalocean.com / ghcr.io / ...
  # username: my-user
  # Always use an access token rather than real password (pulled from .kamal/secrets).
  # password:
  #   - KAMAL_REGISTRY_PASSWORD

# Configure builder setup.
builder:
  arch: amd64
  # Pass in additional build args needed for your Dockerfile.
  # args:
  #   RUBY_VERSION: <%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" %>

# Inject ENV variables into containers (secrets come from .kamal/secrets).
#
# env:
#   clear:
#     DB_HOST: 192.168.0.2
#   secret:
#     - RAILS_MASTER_KEY

# Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
# "bin/kamal app logs -r job" will tail logs from the first server in the job section.
#
# aliases:
#   shell: app exec --interactive --reuse "bash"

# Use a different ssh user than root
#
# ssh:
#   user: app

# Use a persistent storage volume.
#
# volumes:
#   - "app_storage:/app/storage"

# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
# hitting 404 on in-flight requests. Combines all files from new and old
# version inside the asset_path.
#
# asset_path: /app/public/assets

# Configure rolling deploys by setting a wait time between batches of restarts.
#
# boot:
#   limit: 10 # Can also specify as a percentage of total hosts, such as "25%"
#   wait: 2

# Use accessory services (secrets come from .kamal/secrets).
#
# accessories:
#   db:
#     image: mysql:8.0
#     host: 192.168.0.2
#     port: 3306
#     env:
#       clear:
#         MYSQL_ROOT_HOST: '%'
#       secret:
#         - MYSQL_ROOT_PASSWORD
#     files:
#       - config/mysql/production.cnf:/etc/mysql/my.cnf
#       - db/production.sql:/docker-entrypoint-initdb.d/setup.sql
#     directories:
#       - data:/var/lib/mysql
#   redis:
#     image: valkey/valkey:8
#     host: 192.168.0.2
#     port: 6379
#     directories:
#       - data:/data

3-3. VPSのIPアドレスを設定する(最低限の設定)

kamal initで生成されたconfig/deploy.ymlに、VPSのIPアドレスを設定します。これにより、KamalがVPSに接続できるようになります。

設定ファイルの編集

config/deploy.ymlを開いて、serversセクションのhostsにVPSのIPアドレスを設定します。

servers:
  web:
    hosts:
      - 123.45.67.89  # VPSのIPアドレスに置き換える(YOUR_SERVER_IPはVPSのグローバルIPアドレス(例: 123.45.67.89)を表します)

注意:前章でSSH configにホスト名(例: conoha)を設定している場合は、IPアドレスの代わりにホスト名を使用できます。

servers:
  web:
    hosts:
      - conoha  # SSH configで設定したホスト名(YOUR_SSH_HOSTに相当)

補足:YOUR_SSH_HOST~/.ssh/configで定義したSSHホスト名(例: conoha)を表します。以降、この表記が出てきたら自分の環境に合わせて読み替えてください。

設定ファイルの構文チェック

設定ファイルの構文が正しいか確認します。

kamal config

このコマンドで、設定ファイルの構文エラーや設定ミスがないか確認できます。エラーが表示された場合は、設定ファイルを修正する。

VPS接続の確認

kamal server execコマンドを使用して、KamalがVPSに正常に接続できることを確認します。

VPSで任意のコマンドを実行し、接続を確認します。

kamal server exec

期待される出力例

Running 'date' on conoha...
  INFO [58b0869c] Running /usr/bin/env date on conoha
  INFO [58b0869c] Finished in 0.122 seconds with exit status 0 (successful).
...

このように、VPS上でコマンドが実行され、結果が表示されれば、KamalがVPSに正常に接続できていることが確認できます。

VPS接続エラー時の対処法

接続に失敗する場合は、以下を確認します。

  1. SSH接続の確認

    • 前章で設定したSSH接続が正常に動作するか確認。
    ssh conoha  # または ssh -i ~/.ssh/conoha_key.pem root@123.45.67.89
    
  2. 設定ファイルの確認

    • config/deploy.ymlservers.web.hostsに正しいIPアドレスまたはホスト名が設定されているか確認
    • kamal configで構文エラーがないか確認
  3. SSH configの確認

    • ホスト名を使用している場合、~/.ssh/configに正しい設定があるか確認

参考:

3-4. レジストリ設定とシークレットファイルの作成

Kamalは、コンテナイメージをレジストリ(例: GitHub Container Registry)から取得してデプロイします。そのため、レジストリへの認証情報を設定します。

GitHub Container RegistryのPersonal Access Token取得

GitHub Container Registry(GHCR)を使用する場合、Personal Access Token(PAT)を取得します。

  1. GitHubにログインして、Settings → Developer settings → Personal access tokens → Tokens (classic) に移動
  2. 「Generate new token (classic)」をクリック
  3. トークンの名前を入力し、以下のスコープを選択。
    • write:packages - パッケージのアップロードに使用
    • read:packages - パッケージの読み取りに使用
  4. 「Generate token」をクリックしてトークンを生成
  5. 生成されたトークンをコピーして保存(後で確認できないため、この時点で必ず保存してください)

参考:

.kamal/registry_password.keyファイルの作成

  1. .kamal/registry_password.keyファイルを作成します。
echo "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" > .kamal/registry_password.key
chmod 600 .kamal/registry_password.key

注意:ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx の部分を、先ほど取得したGitHub Personal Access Token(PAT)に置き換える。

  1. .kamal/registry_password.keyファイルは機密情報を含むため、必ず.gitignoreに追加します。
echo ".kamal/registry_password.key" >> .gitignore

.kamal/secretsファイルの設定

.kamal/secretsは、デプロイ時に必要なトークンやパスワードを読み込むファイルです。config/deploy.ymlで参照される秘密情報をここに設定します。

重要:.kamal/secretsに直接秘密情報を記載せず、別のファイルから読み込むようにします。

  1. kamal initで生成された.kamal/secretsファイルを開き、以下の内容を追加します。
cat >> .kamal/secrets << EOF
KAMAL_REGISTRY_PASSWORD=$(cat .kamal/registry_password.key)
EOF

これにより、.kamal/registry_password.keyから読み込んだトークンがKAMAL_REGISTRY_PASSWORDとして設定されます。

注意:.kamal/secretsファイルも機密情報を含む可能性があるため、.gitignoreに追加されていることを確認します(kamal initで自動的に追加されるはずです)。

config/deploy.ymlにレジストリ設定を追加

config/deploy.ymlregistryセクションを以下のように設定します。

registry:
  server: ghcr.io
  username: YOUR_GITHUB_USERNAME  # GitHubのユーザー名(例: yourname)を表します
  # Specify the registry server, if you're not using Docker Hub
  # server: registry.digitalocean.com / ghcr.io / ...
  # username: my-user
  # Always use an access token rather than real password (pulled from .kamal/secrets).
  password:
    - KAMAL_REGISTRY_PASSWORD

注意:

  • serverには使用するレジストリのURLを指定します(例: ghcr.io
  • usernameにはGitHubユーザー名を指定します
  • passwordにはKAMAL_REGISTRY_PASSWORDを指定します(.kamal/secretsから読み込まれます)

レジストリ接続の確認

設定したレジストリへの接続を確認します。

kamal registry login

期待される出力例

INFO [21e6f9d5] Running docker --version && docker buildx version as [USERNAME]@localhost
INFO [21e6f9d5] Finished in 0.317 seconds with exit status 0 (successful).
INFO [38b8f7f7] Running docker login ghcr.io -u [REDACTED] -p [REDACTED] as [USERNAME]@localhost
INFO [38b8f7f7] Finished in 3.813 seconds with exit status 0 (successful).
INFO [6ca46bbb] Running docker login ghcr.io -u [REDACTED] -p [REDACTED] on conoha-3
INFO [6ca46bbb] Finished in 3.383 seconds with exit status 0 (successful).

このように、ローカル環境とVPSの両方でレジストリへのログインが成功すれば、レジストリ接続が正常に設定されていることが確認できます。

注意:現時点では、レジストリへの認証が成功することを確認するだけで十分です。

実際のコンテナイメージのビルドやプッシュは、次章(4章)でNext.jsアプリケーションをDocker化してから行います。

また、config/deploy.ymlの完全な設定(サービス名、環境変数、proxy設定など)も、次章でDockerfileを作成・確認した後に行います。

参考:

デプロイ時の注意事項

重要:次章以降でkamal deploykamal setupを実行する際は、変更をGitにコミットしてから実行することをおすすめします。

Kamalはデフォルトで、Gitのコミットハッシュをコンテナイメージのタグとして使用します。そのため、以下の点に注意する:

  • コミットされていない変更は反映されません: ローカルで変更したファイルがコミットされていない場合、その変更はデプロイ対象のイメージに含まれません
  • コミットしてからデプロイ: git addgit commitで変更をコミットしてから、デプロイコマンドを実行する

3章の完了確認

✅ Kamalをインストール(3-1)

config/deploy.ymlを作成(3-2)

✅ VPSのIPアドレスを設定し、接続を確認(3-3)

✅ レジストリ設定とシークレットファイルを作成し、接続を確認(3-4)

4. Next.js アプリケーション準備

この章では、Next.jsアプリケーションをDockerコンテナ化し、ローカル環境で動作確認するまでの手順を紹介します。

Kamalは既にビルド済みのコンテナイメージを使用するため、この章でDockerfileを作成し、その後ローカル環境でコンテナが正常に動作することを確認します。

この章を完了すると、次章でKamalを使ってデプロイする準備が整います。

4章で進めること

  1. 4-1: Next.jsアプリケーションをDocker化(Dockerfileと.dockerignoreの作成)
  2. 4-2: コンテナをローカルで実行して動作確認

注意: Kamalを使用する場合、docker-compose.ymlは不要です。Kamalがコンテナを管理するため、Dockerfileのみで十分です。

4-1. Next.js を Docker 化する(Dockerfile を作成する)

Next.jsアプリケーションをDockerコンテナ化するため、プロジェクトルートにDockerfileを作成します。SSR(Server-Side Rendering)を想定した本番環境用のDockerfileを作成します。

Dockerfile作成前の確認

Dockerfileを作成する前に、以下を確認します:

  • Next.jsプロジェクト: 既存のNext.jsプロジェクトがあること
  • Docker Desktop: ローカル環境でDockerが動作すること

Next.jsプロジェクトの確認

既存のNext.jsプロジェクトが正常に動作することを確認します:

# プロジェクトディレクトリで実行
npm install  # または yarn install
npm run dev  # または yarn dev

ブラウザでhttp://localhost:3000にアクセスすると、アプリケーションが表示されます。

Dockerfileの作成

プロジェクトルートにDockerfileを作成します。本番環境では軽量化が重要なので、マルチステージビルドを使用します:

Dockerfile
# ビルドステージ
FROM node:18-alpine AS builder

WORKDIR /app

# 依存関係ファイルをコピー
COPY package.json yarn.lock* package-lock.json* ./

# 依存関係をインストール
RUN if [ -f yarn.lock ]; then yarn install --frozen-lockfile; \
    elif [ -f package-lock.json ]; then npm ci; \
    else echo "Lockfile not found." && exit 1; \
    fi

# アプリケーションコードをコピー
COPY . .

# 本番用ビルド
RUN npm run build  # または yarn build

# 本番用イメージ
FROM node:18-alpine AS runner

WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

# 非rootユーザーを作成
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# 依存関係ファイルをコピー
COPY package.json yarn.lock* package-lock.json* ./

# 本番用依存関係のみインストール
RUN if [ -f yarn.lock ]; then yarn install --frozen-lockfile --production; \
    elif [ -f package-lock.json ]; then npm ci --only=production; \
    else echo "Lockfile not found." && exit 1; \
    fi

# ビルド結果をコピー
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder --chown=nextjs:nodejs /app/public ./public

# Next.jsの設定ファイルがあればコピー
COPY --from=builder --chown=nextjs:nodejs /app/next.config.js* ./next.config.js*

# 非rootユーザーに切り替え
USER nextjs

# ポート3000を公開
EXPOSE 3000

ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

# アプリケーションを起動
CMD ["node_modules/.bin/next", "start"]

.dockerignoreファイルの作成

Dockerイメージのビルド時に不要なファイルを除外するため、.dockerignoreファイルを作成します。これにより、ビルド時間の短縮とイメージサイズの削減が可能になります。

プロジェクトルートに.dockerignoreファイルを作成します:

.dockerignore
# 依存関係
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# ビルド成果物
.next
out
dist
build

# 環境変数ファイル
.env
.env*.local
.env.development
.env.test
.env.production

# Git関連
.git
.gitignore
.gitattributes

# IDE関連
.vscode
.idea
*.swp
*.swo
*~

# テスト関連
coverage
.nyc_output
*.test.js
*.test.ts
*.test.tsx
*.spec.js
*.spec.ts
*.spec.tsx

# ドキュメント
README.md
CHANGELOG.md
LICENSE
*.md

# その他
.DS_Store
*.log

参考:

4-2. コンテナをローカルで実行して動作確認する

作成したDockerfileでコンテナイメージをビルドし、その後ローカル環境で実行して動作確認します。

コンテナイメージのビルド

プロジェクトルートで以下のコマンドを実行します:

docker build -t my-app .

コンテナの起動(動作確認)

ビルドしたイメージで動作確認します:

docker run --rm -it -p 3000:3000 my-app
  • --rm: コンテナ停止時に自動的に削除する
  • -it: インタラクティブモードで実行(ログを確認しやすくする)
  • -p 3000:3000: ホストのポート3000をコンテナのポート3000にマッピング

注意: Kamalを使用する場合、docker-compose.ymlは不要です。Kamalがコンテナを管理するため、docker runコマンドで直接実行して動作確認するだけで十分です。

動作確認

コンテナが起動したら、ブラウザでhttp://localhost:3000にアクセスすると、Next.jsアプリケーションが表示されます。

4章の完了確認

この章で完了する作業:

  • Dockerfileと.dockerignoreの作成(4-1)
  • ローカル環境でのコンテナ実行と動作確認(4-2)

次章(5章)では、Kamalを使ってVPSにデプロイします。

5. Kamal デプロイ実行(SSL & 公開)

この章では、Kamalを使ってVPSにNext.jsアプリケーションをデプロイします。

初回デプロイではkamal setupコマンドを使用し、2回目以降はkamal deployコマンドを使用します。

この章を完了すると、HTTPSでアクセス可能なアプリケーションが公開されます。

5章で進めること

  1. 5-1: kamal setupで初回デプロイを実行(環境変数の確認を含む)
  2. 5-2: HTTPS経由でアクセスできることを確認する
  3. 5-3: kamal deployで2回目以降のデプロイを実行する

5-1. kamal setup で初回デプロイを実行する

初回デプロイでは、kamal setupコマンドを実行します。

このコマンドは、VPSの初期設定(Dockerインストール、kamal-proxyのセットアップ)とアプリケーションのデプロイを一度に実施します。

デプロイ実行前の確認

kamal setupを実行する前に、以下を確認します。

  • config/deploy.yml: 設定ファイルが正しく設定されていること(3章で完了)
  • Dockerfile: ローカル環境でコンテナが正常に動作すること(4章で完了)

イメージ名の設定

kamal setupを実行する前に、config/deploy.ymlにデプロイするコンテナイメージ名を設定します。

config/deploy.ymlimageに、GitHub Container Registryにプッシュしたイメージ名を設定します。

service: my-nextjs-app
image: YOUR_USERNAME/YOUR_REPO_NAME:latest

注意:

  • imageには、デプロイ時に使用するイメージ名を指定します
  • registry.serverghcr.ioに設定されている場合、imageにはghcr.io/を含めず、YOUR_USERNAME/YOUR_REPO_NAME:latestの形式で指定します
    • Kamalが自動的にghcr.io/を補完します
  • YOUR_REPO_NAMEはリポジトリ名(例: my-nextjs-app)、YOUR_APP_NAMEはコンテナイメージやサービス名として使うアプリ名(例: my-nextjs-app)を表します。以降、これらの表記が出てきたら自分の環境に合わせて読み替えてください

環境変数の設定方法:

環境変数は、.kamal/secretsファイルまたは.kamal/secrets-commonファイルで設定します。

.kamal/secretsファイルの例
# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets,
# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either
# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git.

# Option 1: Read secrets from the environment
KAMAL_REGISTRY_PASSWORD=$(cat .kamal/registry_password.key)

# Option 2: Read secrets via a command
# RAILS_MASTER_KEY=$(cat config/master.key)

# Option 3: Read secrets via kamal secrets helpers
# These will handle logging in and fetching the secrets in as few calls as possible
# There are adapters for 1Password, LastPass + Bitwarden
#
# SECRETS=$(kamal secrets fetch --adapter 1password --account my-account --from MyVault/MyItem KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY)
# KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD $SECRETS)
# RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY $SECRETS)

注意:.kamal/secretsファイルは、機密情報を直接記述せず、環境変数やファイルから読み込む形式で記述します。

proxy設定の追加

HTTPSアクセスを有効にするため、config/deploy.ymlproxy設定を追加します。

proxy:
  ssl: true
  host: <YOUR_DOMAIN>

config/deploy.ymlの最終的な設定例

service: my-nextjs-app

image: YOUR_USERNAME/YOUR_REPO_NAME:latest

servers:
  web:
    - YOUR_SERVER_NAME  # または IPアドレス(YOUR_SERVER_NAMEはKamalのserversで使う任意のホスト名(例: web-1)を表します)

proxy:
  ssl: true
  host: <YOUR_DOMAIN>
  app_port: 3000

registry:
  server: ghcr.io
  username: YOUR_GITHUB_USERNAME
  password:
    - KAMAL_REGISTRY_PASSWORD

builder:
  arch: amd64

ssh:
  user: YOUR_SSH_USER  # VPSにSSH接続する際のユーザー名(例: deploy)を表します
  port: 22

注意:

  • imageには、GitHub Container Registryにプッシュした実際のイメージ名を指定します
  • hostには、前章で設定したドメイン名を指定します。この設定により、kamal-proxyがLet's EncryptからSSL証明書を自動的に取得します

kamal setupの実行

初回デプロイを実行します。

kamal setup

このコマンドで以下が自動的に実行されます。

  1. VPSの初期設定

    • Dockerのインストール(未インストールの場合)
    • kamal-proxyのセットアップと起動
    • Dockerネットワークの作成
  2. アプリケーションのデプロイ

    • コンテナイメージのプル(GitHub Container Registryから)
    • アプリケーションコンテナの起動
    • kamal-proxyとの連携設定
    • ヘルスチェックの実行

デプロイが成功すると、✓ Finished all in XX secondsというメッセージが表示されます。

デプロイ状態の確認

デプロイが完了したら、アプリケーションの状態を確認します。

# アプリケーションの詳細情報を確認
kamal app details

# 実行中のコンテナを確認
kamal app containers

# アプリケーションのログを確認
kamal app logs

トラブルシューティング

デプロイに失敗する場合は、以下を確認します。

  • 設定ファイルの確認: kamal configで設定ファイルの構文エラーがないか確認
  • 環境変数の確認: .kamal/secretsファイルにアプリケーションで使用する環境変数(データベース接続情報など)が設定されているか確認
  • VPS接続の確認: kamal server exec "date"でVPS接続が正常か確認
  • レジストリ接続の確認: kamal registry loginでレジストリ接続が正常か確認

参考:

5-2. HTTPS 経由でアクセスできることを確認する

デプロイが完了し、SSL証明書が取得されたら、https://<YOUR_DOMAIN>でアクセスできることを確認します。

ブラウザでの確認

ブラウザで以下のURLにアクセスします。

  • https://<YOUR_DOMAIN> - ドメイン経由でのHTTPSアクセス
  • http://<YOUR_DOMAIN> - HTTPアクセス(HTTPSにリダイレクトされることを確認)

ブラウザのアドレスバーに鍵アイコンが表示されれば、SSL証明書が正常に取得され、HTTPSでアクセスできることが確認できます。

curlコマンドでの確認

コマンドラインからも確認できます。

# HTTPSアクセスの確認
curl -I https://<YOUR_DOMAIN>

期待される出力例

HTTP/2 200
server: kamal-proxy
date: ...

このように、https://<YOUR_DOMAIN>でHTTPステータスコード200が返ってきて、アクセスできることを確認できれば、デプロイは成功しています。

5-3. kamal deploy で2回目以降のデプロイを実行する

2回目以降のデプロイでは、kamal deployコマンドを使用します。

このコマンドは、アプリケーションの更新のみを行い、VPSの初期設定は行いません。

kamal deployの実行

アプリケーションを更新する場合は、以下のコマンドを実行します。

kamal deployコマンド

kamal deploy

このコマンドで以下が実行されます。

  1. コンテナイメージの更新

    • 最新のコンテナイメージをプル
    • 既存のコンテナを停止
    • 新しいコンテナを起動
  2. ゼロダウンタイムデプロイ

    • 新しいコンテナが正常に起動するまで、既存のコンテナを維持
    • ヘルスチェックが成功したら、既存のコンテナを停止

デプロイの進行状況確認

kamal deployの実行中も、進行状況が表示されます。デプロイが成功すると、✓ Finished all in XX secondsというメッセージが表示されます。

5章の完了確認

この章で完了する作業

  • kamal setupで初回デプロイを実行(5-1)
  • HTTPS経由でアクセスできることを確認(5-2)
  • kamal deployで2回目以降のデプロイを実行(5-3)

次章(6章)では、GitHub Actionsを使ってデプロイを自動化します。

6. GitHub Actions 自動化(CI/CD)

この章では、GitHub Actionsを使って、コードをプッシュするだけで自動的にビルド・デプロイが実行されるCI/CDパイプラインを構築します。

この章を完了すると、手動デプロイの手間がなくなり、コードをプッシュするだけで自動的にデプロイが実行されます。

6章で進めること

  1. 6-1: デプロイ用SecretsをGitHubに登録する(VPS_SSH_KEY、KAMAL_REGISTRY_PASSWORD)
  2. 6-2: CI/CD用のWorkflowファイルを作成する
  3. 6-3: Pushでビルド→プッシュ→デプロイが走ることを確認する

6-1. デプロイ用 Secrets を GitHub に登録する

GitHub ActionsからVPSにデプロイするため、認証情報をGitHub Secretsに登録します。

登録するSecrets

GitHubリポジトリのSettings → Secrets and variables → Actionsで以下を設定します。

  • VPS_SSH_KEY: VPSへのSSH接続用の秘密鍵(1章で設定した鍵を使用)
  • KAMAL_REGISTRY_PASSWORD: GitHub Container RegistryのPersonal Access Token(2-4章で作成したもの)

6-2. CI/CD 用の Workflow ファイルを作成する

GitHub Actionsのワークフローファイルを作成します。このワークフローで、コードをプッシュすると自動的にビルド・デプロイが実行されます。

.github/workflows/deploy.ymlファイルを作成します。

name: Deploy to Production

on:
  push:
    branches:
      - main

defaults:
  run:
    shell: bash

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref }}
  cancel-in-progress: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true

      - name: Install Kamal
        run: gem install kamal

      - name: Enable BuildKit
        run: echo "DOCKER_BUILDKIT=1" >> "$GITHUB_ENV"

      - name: Create `.kamal/registry_password.key`
        run: |
          mkdir -p .kamal
          echo "${{ secrets.KAMAL_REGISTRY_PASSWORD }}" > .kamal/registry_password.key
          chmod 600 .kamal/registry_password.key

      - name: Start ssh-agent
        uses: webfactory/ssh-agent@v0.9.0
        with:
          ssh-private-key: ${{ secrets.VPS_SSH_KEY }}

      - name: Setup SSH
        run: |
          mkdir -p ~/.ssh
          chmod 700 ~/.ssh
          cat >> ~/.ssh/config << EOF
          Host YOUR_SSH_HOST  # ~/.ssh/configで定義したSSHホスト名(例: conoha)を表します
            HostName YOUR_SERVER_IP  # VPSのグローバルIPアドレス(例: 123.45.67.89)を表します
          EOF
          chmod 600 ~/.ssh/config

      - name: Check and release deploy lock
        run: |
          echo "Checking lock status..."
          kamal lock status || echo "No lock found or error checking status"
          echo "Releasing any existing locks..."
          kamal lock release || echo "No lock to release"
        continue-on-error: true

      - name: Deploy with Kamal
        run: kamal deploy

ワークフローの説明

  • トリガー: mainブランチへのプッシュ
  • 並行実行制御: concurrencyで同じブランチでの並行実行をキャンセル
  • タイムアウト: 10分でタイムアウト
  • Rubyセットアップ: Ruby 3.2をセットアップ(KamalはRuby gem)
  • Kamalのインストール: gem install kamalでKamalをインストール
  • BuildKit: Docker BuildKitを有効化
  • Secrets管理: .kamal/registry_password.keyファイルにKAMAL_REGISTRY_PASSWORDを設定
  • SSH認証: webfactory/ssh-agentアクションを使用してSSH鍵を管理
  • SSH設定: SSH configファイルを作成してVPSに接続
  • デプロイロック: kamal lock releaseでロックを解除(ロックされていてもデプロイできるようにする)

注意

  • YOUR_SSH_HOSTYOUR_SERVER_IPは、config/deploy.ymlで設定したSSH接続情報に置き換える
  • config/deploy.ymlserversセクションで指定したHost名をYOUR_SSH_HOSTに、サーバーのIPアドレスをYOUR_SERVER_IPに設定します
  • 上記のワークフローファイル内のコメントに記載されています
    • YOUR_SSH_HOST: ~/.ssh/configで定義したSSHホスト名(例: conoha
    • YOUR_SERVER_IP: VPSのグローバルIPアドレス(例: 123.45.67.89
  • webfactory/ssh-agentアクションにより、SSH鍵が自動的にssh-agentに追加され、Kamalが使用できます
  • kamal lock releaseにより、デプロイロックが存在する場合でもデプロイを実行できます
  • continue-on-error: trueにより、ロックが存在しない場合でもエラーになりません

6-3. Push でビルド→プッシュ→デプロイが走ることを確認する

ワークフローファイルを作成したら、mainブランチにプッシュして動作を確認します。

git add .github/workflows/deploy.yml
git commit -m "Add GitHub Actions workflow for automatic deployment"
git push origin main

GitHubリポジトリのActionsタブで、ワークフローの実行状況を確認できます。mainブランチへのプッシュで自動的にデプロイが実行されます。

デプロイの確認

デプロイが完了したら、以下で確認します。

# ローカルからVPSの状態確認
kamal app details

# ブラウザでアクセス確認
# https://<YOUR_DOMAIN>

6章の完了確認

この章で完了する作業

  • デプロイ用SecretsをGitHubに登録(VPS_SSH_KEYKAMAL_REGISTRY_PASSWORD
  • CI/CD用のWorkflowファイルを作成
  • Pushでビルド→プッシュ→デプロイが走ることを確認

これで、コードをプッシュするだけで自動的にデプロイが実行されるCI/CDパイプラインが完成しました。

次は、Cloudflare 経由でアクセスできるようにしていきます。(執筆中)

参考資料

https://zenn.dev/naofumik/articles/8849c2e8feecc0
https://zenn.dev/isawa/articles/a721641613f013

  • ゼロからはじめるLinuxサーバー構築・運用ガイド第2版動かしながら学ぶWebサーバーの作り方

https://www.amazon.co.jp/dp/B0CTQPNWZ8

  • GitHub CI/CD実践ガイド ――持続可能なソフトウェア開発を支えるGitHub Actionsの設計と運用

https://www.amazon.co.jp/dp/B0D4DBYJJ9

Discussion