GitHub Pages でソースリポジトリと公開用リポジトリを分ける

前提 #

Configuring a publishing source for your GitHub Pages site - GitHub DocsYou can configure your GitHub Pages site to publish when changes are pushed to a specific branch, or you can write a GitHub Actions workflow to publish your site.GitHub Docs

現在利用できる GitHub Pages のデプロイ手法は 2 種類あります。

GitHub は後者の GitHub Actions によるデプロイを推奨しています。こちらはデプロイのタイミングや手続きを GitHub Actions ワークフローの表現力で柔軟に構成でき、成果物 (HTML など) をリポジトリにプッシュする必要がないため、より汎用的かつ効率的で第一選択肢となるでしょう。

ソースリポジトリの分離 #

一般的な Pages のデプロイ戦略は、SSG ツールの構成とソースコード (JSX, Markdown など) を含む単一のリポジトリにおいて GitHub Actions ワークフローを構成する方法です。他方、いくつかのシナリオにおいては、このようなソースを含むリポジトリと、Pages の公開設定を行うリポジトリを分離したい場合もあります。例えば、単純にセキュリティレベルや責務を分離するためや、複数リポジトリに依存関係をもつビルドプロセスを独立させるため、などが考えられます。このようなケースでは、前述のデプロイ手法のいずれを用いるかによって、実装方針が異なります。

なお、以降では便宜上、Pages の公開設定を行わないリポジトリを「ソースリポジトリ」、公開設定を行うリポジトリを「公開用リポジトリ」と呼称します。

  1. Publish from branch
    • このデプロイ手法においては、成果物を公開用リポジトリにプッシュする必要があります。
    • ソースリポジトリでビルドを行ったのち、公開用リポジトリへその内容をプッシュするようなワークフローを実装することになるでしょう。
    • 利点:
      • 成果物(すなわち Web ページとして HTTP リクエスト可能なリソース)以外のすべてをソースリポジトリに分離できること
      • 公開用リポジトリにワークフロー実装が不要であり、結果として、公開用リポジトリからソースリポジトリへのアクセスが不要であること
  2. Publish from GitHub Actions
    • このデプロイ手法においては、公開用リポジトリで pages 用の workflow artifact を作成し、それをデプロイする必要があります。
    • デプロイ手法固有の利点として、成果物のリポジトリへの push が不要である性質は受け継がれます。
    • アプローチは 2 種類考えられます:
      • a. 公開用リポジトリのワークフローでソースリポジトリを clone し、その内容を用いてビルドおよび pages のデプロイを行う
        • この手法の欠点は、公開用リポジトリにおいてソースリポジトリコンテンツへのアクセスが必要であること、また、ビルドプロセスを公開用リポジトリに実装する必要があることです。
      • b. ソースリポジトリのワークフローで成果物を workflow artifact をアップロードし、公開用リポジトリのデプロイワークフローを呼び出す。呼び出されたデプロイワークフローでは、ソースリポジトリの workflow artifact をダウンロードし、それを pages にデプロイする
        • この手法の利点は、2-a. の手法の欠点を解消し、公開用リポジトリのワークフローで取り扱われる情報は公開される成果物のみとすることができることです。

ソースリポジトリを分離する動機に照らして、とりうる実装方針は実質的に 1. か 2-b. のいずれかになるでしょう。以降ではこの 2 種類の実装をそれぞれ例示します。

“Publish from branch” の場合でソースリポジトリを分離する #

flowchart LR subgraph ghp-pub artifacts["/docs/*"] end subgraph ghp-src sources["Source files"] w["push.yml"] end Developer Developer -->|1. Push| sources Developer -.->|2. On push| w sources -->|3. Checkout| w w -->|4. Build & Push| artifacts ghp-pub -.->|5. On push| Pages
.github/workflows/push.yml
name: Push

on:
  push:
    branches:
      - main

jobs:
  push:
    permissions:
      contents: read
    runs-on: ubuntu-latest
    steps:
      # ソースリポジトリを取得します。
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
      # 公開する Web ページをビルドします。
      # ここでは例として HTML を作成しているだけですが、実際には SSG ツールの呼び出しなどを行うでしょう。
      - name: Build
        run: |
          mkdir -p docs
          touch docs/.nojekyll
          echo '<h1>Hello GitHub Pages ${{ github.sha }}</h1>' > docs/index.html
      # GitHub App Installation Access Token を取得します。
      - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
        id: auth
        with:
          app-id: ${{ secrets.APP_ID }}
          owner: ${{ vars.PAGES_REPO_OWNER }}
          private-key: ${{ secrets.APP_PRIVATE_KEY }}
          repositories: ${{ vars.PAGES_REPO_NAME }}
      # 公開用リポジトリを取得します。
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          path: public
          persist-credentials: false
          repository: ${{ vars.PAGES_REPO_OWNER }}${{ '/' }}${{ vars.PAGES_REPO_NAME }}
          token: ${{ steps.auth.outputs.token }}
      # 公開用リポジトリにビルド成果物をプッシュします。
      - name: Configure git user info
        env:
          GH_TOKEN: ${{ steps.auth.outputs.token }}
        run: |
          USER_NAME='${{ steps.auth.outputs.app-slug }}[bot]'
          USER_ID="$(gh api "/users/$USER_NAME" --jq .id)"
          git config user.name "$USER_NAME"
          git config user.email "$USER_ID+$USER_NAME@users.noreply.github.com"
      - name: Push
        working-directory: public
        run: |
          rm -fr docs
          cp -r ../docs .
          git add .
          git commit -m 'publish'
          git remote set-url origin 'https://x-access-token:${{ steps.auth.outputs.token }}@github.com/${{ vars.PAGES_REPO_OWNER }}/${{ vars.PAGES_REPO_NAME }}.git'
          git push

この実装例では、ghp-src の main ブランチにプッシュするたび push.yml ワークフローが実行され、ghp-pub の main ブランチに成果物がプッシュされます。Pages へのデプロイは ghp-pub の設定に基づいて暗黙的に行われ、ワークフローの実装はありません。

なお、GitHub App を利用した他リポジトリへのプッシュについては GitHub Actions でコミットに署名する で詳しく議論しているためあわせて参照してください。

“Publish from GitHub Actions” の場合でソースリポジトリを分離する #

flowchart LR subgraph ghp-pub deploy["deploy.yml"] pages_artifacts["Workflow artifact for pages"] end subgraph ghp-src sources["Source files"] push["push.yml"] artifacts["Workflow artifact"] end Developer -->|1. Push| sources Developer -.->|2. On push| push sources -->|3. Checkout| push push -->|4. Build & Upload| artifacts push -.->|5. Dispatch| deploy artifacts -->|6. Download| deploy deploy -->|7. Upload| pages_artifacts deploy -.->|8. Deploy| Pages pages_artifacts --> Pages

(ghp-src) .github/workflows/push.yml
name: Push

on:
  push:
    branches:
      - main

jobs:
  build:
    permissions:
      contents: read
    runs-on: ubuntu-latest
    steps:
      # ソースリポジトリを取得します。
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
      # 公開する Web ページをビルドします。
      # ここでは例として HTML を作成しているだけですが、実際には SSG ツールの呼び出しなどを行うでしょう。
      - name: Build
        run: |
          mkdir -p docs
          touch docs/.nojekyll
          echo '<h1>Hello GitHub Pages ${{ github.sha }}</h1>' > docs/index.html
      # 成果物をアップロードします。
      - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        id: artifact
        with:
          path: _site/
          retention-days: 1
      # GitHub App Installation Access Token を取得します。
      - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
        id: auth
        with:
          app-id: ${{ secrets.APP_ID }}
          owner: ${{ vars.PAGES_REPO_OWNER }}
          private-key: ${{ secrets.APP_PRIVATE_KEY }}
          repositories: ${{ vars.PAGES_REPO_NAME }}
      # 公開用リポジトリのデプロイワークフローを呼び出します。
      - name: Dispatch deploy workflow
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
        with:
          github-token: ${{ steps.auth.outputs.token }}
          script: |
            await github.rest.actions.createWorkflowDispatch({
              owner: '${{ vars.PAGES_REPO_OWNER }}',
              repo: '${{ vars.PAGES_REPO_NAME }}',
              workflow_id: 'deploy.yml',
              ref: '${{ vars.PAGES_REPO_BRANCH }}',
              inputs: {
                artifact_id: '${{ steps.artifact.outputs.artifact-id }}',
                run_id: '${{ github.run_id }}'
              }
            })
(ghp-pub) .github/workflows/deploy.yml
name: Deploy

on:
  workflow_dispatch:
    inputs:
      artifact_id:
        description: The ID of the source artifact
        required: true
        type: string
      run_id:
        description: The ID of the run containing the source artifact
        required: true
        type: string

jobs:
  prepare:
    runs-on: ubuntu-latest
    steps:
      # GitHub App Installation Access Token を取得します。
      - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
        id: auth
        with:
          app-id: ${{ secrets.APP_ID }}
          owner: ${{ secrets.PAGES_SOURCE_REPO_OWNER }}
          private-key: ${{ secrets.APP_PRIVATE_KEY }}
          repositories: ${{ secrets.PAGES_SOURCE_REPO_NAME }}
      # ソースリポジトリでビルドした成果物をダウンロードします。
      - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
        with:
          artifact-ids: ${{ inputs.artifact_id }}
          github-token: ${{ steps.auth.outputs.token }}
          path: _site/
          repository: ${{ secrets.PAGES_SOURCE_REPO_OWNER }}${{ '/' }}${{ secrets.PAGES_SOURCE_REPO_NAME }}
          run-id: ${{ inputs.run_id }}
      # 改めて、このリポジトリの workflow artifact としてアップロードします。
      - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
  # Pages へデプロイします。このジョブは単一リポジトリの場合とまったく同じです。
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    needs: prepare
    permissions:
      id-token: write
      pages: write
    runs-on: ubuntu-latest
    steps:
      - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
        id: deployment

この実装例では、ghp-src の main ブランチにプッシュするたび、push.yml ワークフローが実行され、ページのビルドおよび成果物のアップロードが行われます。さらに、そこから GitHub App の権限を利用して ghp-pub の deploy.yml ワークフローを起動します。deploy.yml ワークフローでは、ソースリポジトリのビルド済み成果物をダウンロードし、それを ghp-pub の pages デプロイ用 workflow artifact として改めてアップロードし、これを pages にデプロイします。

なお、push.yml ワークフローから直接 ghp-pub の workflow artifact をアップロードすると冗長さが減少できそうに思いますが、現状では API 的に不可能なようでした。

まとめ #

GitHub Pages のソースリポジトリと公開用リポジトリを分離したデプロイ戦略について 2 パターンを提案しました。