GitHubにPushするだけで自宅サーバーへ自動デプロイ|GitHub Actions × HomeGrid VPN

外部のサーバーが中継クラウドを通じて家の中のサーバーにつながるイラスト

GitHub ActionsのSelf-hosted Runnerをデプロイ先に届く場所へ置けば、GitHubへのpushをきっかけに、インターネットへSSHを公開していない自宅サーバーにも自動デプロイできます。Runnerが自宅LANの外にある場合も、常時接続のVPN経路を用意すれば、ジョブごとにVPNを接続する処理を省けます。ポート開放の可否に悩む前に、ジョブを実行する場所と、デプロイ先までの経路を分けて考えてみましょう。

自宅サーバーへデプロイしたい。でも外から接続できない

手元で作ったアプリを、自宅のミニPCや検証サーバーで動かす。コードをGitHubへpushしたら、テストして、そのまま自動で更新したい。

ところが、GitHubが用意する通常のGitHub-hosted Runnerから自宅サーバーへSSH接続しようとすると、ネットワークの壁にぶつかります。宛先に192.168.x.xなどのプライベートIPを指定しても、別途経路を用意しなければ自宅LANへは直接届きません。非公開のネットワークに置いたVPSやVPC内のサーバーも同じです。VPSにPublic IPがあってSSHを許可している場合は、公開経路で接続できます。

Public IPv4とポート転送を使える回線なら、SSHを外部から受け付ける構成は作れます。ただし、家庭用回線には次のような条件があります。

  • CGNATやDS-Liteでは、利用者が上位NATのポート転送を設定できないことがあります。
  • MAP-Eでは、利用できるIPv4ポートが割り当てられた範囲に制限される場合があります。
  • 二重NATや集合住宅の上位ルーターによって、自分のルーターの設定だけでは届かないことがあります。
  • 動的なPublic IPを接続先として使うなら、アドレス変更への対応も必要です。

IPv6で直接接続する方法もあります。その場合は、自宅側のGlobal IPv6、接続元のIPv6接続性、ルーターとサーバーOSのInbound許可を確認します。IPv6でNATのポート転送が不要になっても、Inboundの設計は残ります。

ただ、自動デプロイに必要なのは「インターネットのどこからでもSSHできること」ではありません。デプロイを実行するRunnerから、対象サーバーへ届くことです。

まずはSelf-hosted Runnerを自宅LANに置く方法がある

Self-hosted Runnerは、自分で用意したマシン上でGitHub Actionsのジョブを実行する仕組みです。

RunnerはGitHubへ外向きに接続し、ジョブを受け取ります。GitHubから自宅へ新しい接続を受け付けるために、自宅ルーターのポートを開ける必要はありません。必要な通信先へのOutbound HTTPSなどは許可しておく必要があります。GitHub公式の通信要件

たとえば、自宅LAN内にRunner用のLinuxマシンを置けば、次の流れを作れます。

開発用PC → GitHubへpush
                 ↑
          Runnerから外向きに接続
                 │
          自宅LAN内のRunner
                 │ SSHなど
                 ↓
          非公開の自宅サーバー

この構成なら、自宅LANへのVPNも必須ではありません。自宅LAN内のRunnerから対象へ接続できればよいからです。対象サーバー自身にRunnerを入れ、ローカルで更新する方法もあります。

一方、別の場所にもデプロイしたい、RunnerをVPSへ置きたい、ジョブの実行環境を本番サーバーから分離したい場合は、Runnerから各接続先へ届く経路が必要になります。

Runnerとデプロイ先は、別のマシンでよい

Runnerはジョブを実行するマシンです。デプロイ先は、更新したアプリを動かすサーバーです。両者を同じマシンにする必要はありません。

役割 担当すること
Runner コードの取得、テスト、ビルド、成果物の転送、更新コマンドの実行
デプロイ先 アプリやサービスの実行
ネットワーク経路 Runnerとデプロイ先の間の通信

たとえば、VPS上のRunnerから自宅の検証サーバーへSSHでデプロイできます。自宅のRunnerから、別拠点やクラウド内のサーバーへデプロイする構成も考えられます。

VPNは、このうちネットワーク経路を用意します。SSH認証やアプリの更新処理まで代わりに実行するものではありません。

VPNをジョブの中で接続する方法と、常時接続する方法

GitHub-hosted Runnerから、ジョブの開始時にVPNへ参加し、デプロイ後に切断する方法もあります。VPN製品や構成によって、短命な認証情報やOIDCなどを利用する設計も可能です。

一方で、VPNへの登録、接続完了の待機、経路の確認、認証情報の受け渡しといった処理がワークフローに加わります。

Self-hosted Runnerなら、Runnerのマシンを事前にVPNへ接続し、その接続を維持しておく方法を選べます。

Runnerの準備時:VPNへ登録 → 接続 → サービスとして維持
ジョブの実行時:テスト → ビルド → 非公開サーバーへデプロイ

デプロイジョブは、すでに用意されたネットワーク経路を使います。ジョブごとのVPN接続処理がなくなるので、ワークフローをアプリの更新処理に集中させられます。

ただし、接続状態や経路、Runnerの稼働を監視する仕事は残ります。常時接続とは、障害が起きないという意味ではありません。

HomeGrid VPNでは、Runnerを常時接続のSpokeにする

HomeGrid VPNでこの構成を作る場合、Runner用のLinuxマシンをSpokeとして登録し、VPNへ常時接続しておきます。自宅側には、自宅LANへ接続するGridを用意します。

Runner側と自宅側は、それぞれCloud HubへOutboundで接続します。その経路を使い、許可されたRunnerから自宅LAN内のサーバーへアクセスします。

常時接続Runnerから非公開サーバーへデプロイする経路 GitHub Actionsのジョブを受けたSpoke RunnerからCloud Hub、自宅Grid、自宅サーバーへデプロイ通信が進みます。Runnerと自宅GridはそれぞれCloud Hubへ外向きにVPN接続します。Cloud Hubは中継点であり、図はインターネットの出口を示していません。 GitHub Actionsmain へ push ジョブ 外向き HTTPS Self-hosted RunnerSpoke・常時接続 Cloud HubVPN 中継点 自宅 LAN 自宅 Grid 非公開サーバーSSH でデプロイ VPN 経路
ジョブはRunner上で実行され、緑の矢印の経路でデプロイします。GitHubへのジョブ受信接続はRunnerから開始します。VPN接続の開始方向はRunner → Cloud Hub ← 自宅Gridです。Cloud Hubは中継点であり、この図にInternet Exitはありません。

緑の矢印は、VPNが成立した後にデプロイ通信が進む経路です。Cloud Hubから自宅のPublic IPへ、新たなInbound接続を開始するという意味ではありません。

この構成では、自宅ルーターのIPv4ポート転送、固定Public IP、DDNS、自宅へのIPv6 Direct接続を前提にしません。CGNAT・MAP-E・DS-Liteの回線でも、自宅側からCloud Hubへ必要なOutbound通信を行えることが条件です。回線名だけで接続を保証できるわけではなく、施設やISPの通信制限も確認します。

Linuxマシンの登録手順は、Linux端末をSpokeとして追加するガイドを参照してください。デプロイ用Runnerは、原則として--splitで登録します。 自宅LANとテナントの範囲だけをVPNへ通し、GitHubへのHTTPSやパッケージ・コンテナイメージの取得はRunner自身のインターネット接続を使います。接続先LANを後から追加した場合は、経路の取り直しも確認します。

Linux Spokeの既定はフルトンネルです。これを使うと、Runnerのインターネット向け通信もCloud Hubと自宅回線を経由します。外部に置いたRunnerでも自宅回線やGridの障害でGitHubからジョブを受け取れなくなり、ダウンロードしたデータをRunnerへ届けるために自宅回線の上り帯域も使います。フルトンネルは、Runnerのインターネット向け通信も意図的に自宅から出したい場合に選びます。--splitでも、自宅サーバーへのデプロイには自宅側の稼働が必要です。

CIからVPN接続処理とVPN用トークンを外せる

Runnerを登録し、VPN接続をホスト側のサービスとして維持すれば、通常のデプロイジョブでHomeGrid VPNの登録用トークンを渡したり、接続コマンドを毎回実行したりする必要はありません。

これは「認証情報を一切管理しなくてよい」という意味ではありません。

情報 管理が必要になる場所・場面
VPN登録用トークン RunnerをVPNへ登録するとき。通常のデプロイジョブへ毎回渡す必要はない
VPNの鍵・接続設定 常時接続するRunnerホスト上で保護する
GitHub Runnerの資格情報 GitHubへの登録と継続運用で管理する
SSH秘密鍵・ホスト鍵の確認情報 SSHでデプロイする場合に必要
レジストリやクラウドの資格情報 成果物の取得・配布方法に応じて必要

VPNへの参加処理を、ジョブからRunnerの運用へ移す設計です。Runnerの侵害や廃止時には、GitHub側のRunner登録、VPN側のデバイス登録、SSH鍵などをそれぞれ無効化・交換する必要があります。

手順:経路と認証を用意してからRunnerを登録する

1. Runner用のLinuxマシンを用意する

自宅のLinuxマシンでもVPSでも構いません。GitHubとCloud Hubへの必要なOutbound通信を許可します。Runnerのジョブ実行用には専用の一般ユーザーを用意し、デプロイ先の本番サーバーとは分離しておくと、権限と保守の範囲を整理できます。

2. Spokeとして登録し、自宅Gridへの経路を確認する

Runner用Linuxは、--splitを付けてHomeGrid VPNへSpokeとして登録します。自宅Gridが接続され、対象サーバーのLANが登録されていることを確認してください。デプロイ先サーバー自体をSpokeへ登録する必要はありません。

3. LAN ACLを設定する

Runnerのデバイスから、デプロイ先のLANへのアクセスを許可します。反映後に経路を確認し、不要なLANは許可しません。サーバーやポート単位の制限は、後述するOS Firewallなどで追加します。

4. 公開鍵認証とSSH疎通を確認する

対象にdeployなどの専用ユーザーを作り、公開鍵認証を設定します。Runnerのサービスが使うユーザーのSSH設定・秘密鍵・known_hostsを用意します。対話ログインした管理者の設定だけで成功しても、サービスユーザーでは接続できない場合があります。

ホスト鍵の指紋は対象サーバーのコンソールなど、信頼できる経路で確認してください。ssh-keyscanの出力を確認せず採用するだけでは、初回接続相手の真正性は確認できません。

ssh -o BatchMode=yes -o StrictHostKeyChecking=yes deploy@192.168.1.100 'true'

192.168.1.100は説明用のプライベートIPです。自分の環境の宛先へ置き換えてください。固定Public IPは不要ですが、LAN内の接続先はDHCP予約などで安定させておきます。

SSH認証が成功したら、デプロイ先の/usr/local/bin/deploy-appを用意します。このスクリプトで、適用する成果物の選択・取得、更新、ヘルスチェック、失敗時の復旧を実装し、手動実行を確認します。必要な権限だけを与え、無制限のsudo権限は付けません。

5. GitHubへRunnerを登録して常駐化する

対象の非公開リポジトリで「Settings」→「Actions」→「Runners」→「New self-hosted runner」を開き、Linuxとマシンのアーキテクチャを選びます。画面に表示されたダウンロード・検証・登録コマンドを、Runner専用ユーザーで実行します。カスタムラベルにhomegrid-deployを追加してください。GitHub公式のRunner登録手順

登録完了後、systemdを利用するLinuxではRunnerのインストール先で次を実行し、サービスとして起動します。例のrunnerは事前に用意した専用ユーザー名へ置き換えます。

sudo ./svc.sh install runner
sudo ./svc.sh start
sudo ./svc.sh status

手動で起動していたRunnerは先に停止してください。GitHub側でジョブを受け付けられる状態になったことを確認し、ホスト再起動後もVPNとRunnerの両方が動作することを確かめます。VPNの復旧より先にジョブが始まる場合も、接続できなければデプロイを失敗させる必要があります。GitHub公式のサービス設定手順

Workflow:mainへのpushで非公開サーバーを更新する

次をリポジトリの.github/workflows/deploy.ymlへ置きます。信頼する非公開リポジトリで、保護したmainブランチへのpushをデプロイのきっかけにする最小例です。LinuxのSelf-hosted Runnerにhomegrid-deployという独自ラベルを付けてあるものとします。

name: Deploy private server

on:
  push:
    branches: [main]

permissions:
  contents: read

concurrency:
  group: private-server-deploy
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: [self-hosted, linux, homegrid-deploy]
    timeout-minutes: 15
    steps:
      - name: Deploy over private network
        run: >-
          ssh -o BatchMode=yes -o StrictHostKeyChecking=yes
          -o ConnectTimeout=15
          deploy@192.168.1.100 '/usr/local/bin/deploy-app'

homegrid-deployはジョブの振り分けに使うラベルで、それ自体がアクセス権限を制限する仕組みではありません。Environmentの承認を使う場合は、先にGitHubで対象Environmentと保護ルールを設定し、ジョブに対応するenvironmentも指定します。

この例はデプロイ先のスクリプトを呼ぶだけなので、Runnerへのcheckoutは不要です。contents: readはGitHubトークンの権限上限であり、SSH認証を与える設定ではありません。Runner側でテストやビルドも行うなら、checkout、テスト、ビルド、成果物転送をSSH実行の前へ追加します。利用するActionは確認済みの完全なコミットSHAへ固定してください。

この最小例はpushしたコミットを引数として渡していません。pushと同じ成果物を確実に適用したい場合は、コミットSHAやイメージのdigestで成果物を特定してデプロイ処理へ渡し、対象の版を検証する実装を追加します。サンプルだけでアプリ固有のデプロイ処理が完成するわけではありません。

実行前には、Runnerからデプロイ先へ経路があり、必要な認証情報を利用できることを確認します。VPN接続をジョブ内で開始しなくても、接続先に到達できない場合は更新せず失敗させる設計が必要です。

更新中のサービス停止を抑える方法や、失敗時のロールバックもアプリ側で用意します。同時デプロイの抑制は、ジョブ開始順やリリース順の完全な保証にはならないため、必要なら古い成果物を適用しない確認も追加します。

自宅だけでなく、VPSやVPCにも展開できる

同じ考え方は、自宅サーバー以外にも使えます。

  • インターネットへ管理用SSHを公開したくないVPS。
  • AWSのVPCやAzureのVNet内にあるプライベートサーバー。
  • 事務所の検証環境や、別拠点の開発サーバー。

HomeGrid VPNでは、追加先LAN内のLinux端末をゲートウェイとして、拠点LANやVPCを接続する構成を取れます。Runnerから、そのLANへの経路とアクセス許可を用意し、対象へデプロイします。

VPCを追加しただけで、すべてのサブネットへ自動的に届くとは限りません。ゲートウェイから対象への到達性、登録したネットワーク範囲、Security Group、ネットワークACL、OSのFirewallなどを確認してください。別拠点とアドレス範囲が重複する場合も、経路設計が必要です。

接続先の追加方法と条件は、自宅・事務所・VPCのLANをつなぐ記事で説明しています。

VPN経路をそろえても、OS、デプロイ方式、アプリの設定、クラウドIAMまで共通になるわけではありません。共通化できるのは、まず「Runnerから非公開環境へ届く」という部分です。

LAN ACLはサブネット単位。SSH認証とFirewallも必要

HomeGrid VPNのLAN ACLでは、Runnerとして登録したデバイスから、どのLANへアクセスを許可するかを設定します。

許可の単位はLAN・サブネットです。 「このサーバーのSSHポートだけを許可する」といった機器・ポート単位の制御を、LAN ACLだけで実現するものとして説明することはできません。

デプロイ用途では、次の役割を分けます。

層 制御すること
LAN ACL RunnerがアクセスできるLAN・サブネット
OS Firewallなど 対象サーバーで受け付ける通信・ポート
SSH認証 接続するユーザーと鍵
OSの権限 変更できるファイル、実行できるコマンド

デプロイ先には専用ユーザーを用意し、不要な管理者権限を与えないようにします。SSHのホスト鍵は信頼できる方法で事前確認し、接続確認を省くためにホスト鍵検証を無効化しないでください。

Firewallの送信元条件は、接続先で実際に観測されるアドレスに合わせます。ゲートウェイやNATがある構成で、RunnerのVPNアドレスがそのまま見えると決めつけないことが大切です。

同じサブネットに家族のPCやNASがある場合は、LANへの許可範囲も意識します。より強い分離が必要なら、デプロイ対象を専用のサブネットやVLANへ分ける設計も検討できます。LAN ACLの設定ガイド

常時接続Runnerを、信頼境界として管理する

Runner上のジョブは、Runnerが持つネットワーク経路や権限を利用できます。自宅LANへ接続できるRunnerで信頼できないコードを実行すると、その接続先へ横展開する足がかりになるおそれがあります。Private Repositoryでの運用を推奨しますが、非公開にするだけで安全になるわけではありません。

デプロイ用Runnerでは、少なくとも次を確認します。

  • 外部からのPull Requestや、未検証のコードを実行させない。
  • 利用できるリポジトリを絞り、提供される範囲でRunner groupなどの制限を使う。
  • mainブランチとワークフロー変更を保護し、必要に応じてEnvironmentの承認を使う。
  • OS、Runner、依存ソフトウェアを更新し、ジョブが残したファイルやプロセスも管理する。
  • ジョブを不用意にrootで動かさず、Dockerソケットなど強い権限へのアクセスを限定する。
  • VPNの鍵とデプロイ用秘密情報を保護し、ログや成果物へ出力しない。

非公開リポジトリでも、実行されるコードや変更権限の確認は必要です。侵害された常設Runnerに変更が残り、次のジョブへ影響する可能性もあります。GitHubもSelf-hosted Runnerにおける信頼できないコードの実行リスクを説明しています。GitHubのRunner安全運用に関する公式資料

検証用のビルドを別環境で行い、デプロイ用Runnerには確認済み成果物の配布だけを担当させる方法もあります。より強い実行環境の分離が必要なら、ジョブごとに環境を破棄するRunner構成も選択肢です。

Cloud Hubとインターネットの出口は別

この構成のCloud Hubは、Runnerと自宅側をつなぐVPN経路上の構成要素です。Cloud Hubを通ることと、インターネット向け通信がCloud HubのPublic IPから出ることは同じではありません。

自宅LANのサーバーへデプロイする通信は、そのサーバーが宛先です。家庭用回線をインターネットのExitとして使う必要はありません。

一方、Runnerのインターネット向け通信も自宅から出すフルトンネルを選んだ場合は、Cloud Hub、自宅側Grid、家庭用ISPを経由してインターネットへ出ます。その場合の出口は自宅回線です。--splitでは、対象LANなどへの通信と、それ以外のインターネット向け通信を分けます。

Cloud Hub ≠ Internet Exit。 接続先LANへの経路、アクセス許可、インターネットの出口は、それぞれ確認してください。Cloud Hubも運用と信頼の対象に含まれます。VPNを使っても、接続先の認証や権限制御が不要になるわけではありません。

pushからデプロイまでを、公開ポートに依存させない

自宅LAN内にRunnerを置くなら、VPNなしでも非公開サーバーへの自動デプロイは作れます。Runnerを外部へ置いたり、複数の拠点へ接続したりする場合は、VPNなどで必要な経路を用意します。

HomeGrid VPNを使う構成では、常時接続するSpoke Runnerから、許可した自宅LANや拠点LANへアクセスします。自宅のポート開放、固定Public IP、DDNSを前提にせず、ジョブごとのVPN接続処理とVPN登録用トークンの受け渡しを省けます。

コードをGitHubへpushし、テストとビルドを経て、非公開サーバーへ更新を届ける。接続先が自宅でもVPSでもVPCでも、まず必要なのは、Runnerから届く経路と、更新に必要な最小限の権限です。

導入前には、Runnerをどこに置くか、どのLANへのアクセスを許可するか、SSHで何を変更できるようにするかを決めてください。そのうえで、手動デプロイ、失敗時の停止・復旧、自動実行の順に確認すると、運用まで見通した構成にできます。