# APIトークンを持たずにGitHub ActionsのビルドをCloudflare Workersへデプロイする

Date: 2026-11-04
URL: https://s.conao3.com/blog/2026/12e5f6
Author: conao3 (Naoya Yamashita)

GNU Emacsのマニュアルを日本語に機械翻訳したサイト<https://emacs-doc.conao3.com>を、Cloudflare Workersで公開しています。
[conao3/emacs-doc](https://github.com/conao3/emacs-doc)はpublicのリポジトリです。
このサイトはGitHubにもCloudflareにもAPIトークンを置かずにデプロイしています。
ビルドはGitHub Actionsで行い、生成物はGitHubのReleaseの添付ファイルとして置きます。
デプロイはCloudflareのWorkers Buildsが、その添付ファイルを取って行います。
この記事では、この構成とその性質を紹介します。

![img](https://s.conao3.com/img/12e5f6-flow.svg)

# よくある方法はトークンをSecretsに置く

GitHub ActionsからWorkersへデプロイする方法は、Cloudflareの公式の手順[GitHub Actions](https://developers.cloudflare.com/workers/ci-cd/external-cicd/github-actions/)に書かれています。
この手順では、 `CLOUDFLARE_API_TOKEN` と `CLOUDFLARE_ACCOUNT_ID` をリポジトリのSecretsに置き、 `cloudflare/wrangler-action` に渡します。
po4aで文書を翻訳してサイトを作る[Netatalk/netatalk.io](https://github.com/Netatalk/netatalk.io)も、同じようにトークンをSecretsに置いてデプロイしています。

GitHubのOIDCを使えば、クラウドによってはトークンを置かずに済みます。
しかし、CloudflareはGitHubのOIDCを受け付けません。

# Workers Buildsではemacs-docをビルドできない

Workers BuildsはCloudflareのGit連携の機能です。
GitHubのリポジトリをつなぐと、監視するブランチへpushされるたびにCloudflare側でbuild commandとdeploy commandを実行します。
deploy commandの既定は `npx wrangler deploy` で、GitHub側にCloudflareのトークンは要りません。

ただし、emacs-docのサイトはWorkers Buildsの環境ではビルドできません。
emacs-docのビルドでは、Texinfoのマニュアルを `makeinfo --html` でHTMLにし、po4aで訳を差し込みます。
Workers Buildsのビルドイメージ[Build image](https://developers.cloudflare.com/workers/ci-cd/builds/build-image/)はUbuntu 24.04で、NodeやPythonは入っていますが、texinfo・po4a・gettextは入っていません。
また、[Limits & pricing](https://developers.cloudflare.com/workers/ci-cd/builds/limits-and-pricing/)のとおり、1回のビルドは20分までです。
emacs-docでは `make pot` と `make site` だけでそれぞれ約5分かかります。

# ビルドとデプロイを分ける

そこで、ビルドはGitHub Actions、デプロイはWorkers Buildsに分けました。
Actionsは名前を固定したリリース `site` に添付ファイル `dist.tar.zst` を上げ、Workers BuildsはそのSHA-256を書いたファイル `SITE_SHA256` と照合してデプロイします。

## 1. Actionsがサイトを作ってReleaseに上げる

Actionsの `Update` ワークフローは `workflow_dispatch` で手動で回します。
サイトを `dist/` に作ったあと、 `make package` でtarにします。

```makefile
package:
        mkdir -p build
        tar --sort=name --mtime=@0 --owner=0 --group=0 --numeric-owner --pax-option=delete=atime,delete=ctime \
          -C $(DIST_DIR) -cf build/dist.tar .
        zstd -q -19 -f --rm -o $(SITE_ARCHIVE) build/dist.tar
        sha256sum $(SITE_ARCHIVE) | cut -d" " -f1 > SITE_SHA256
```

tarのオプションは、同じ内容のファイルから同じバイト列を作るためのものです。
ファイルを名前の順に並べ、更新時刻と所有者・グループを0に固定し、paxヘッダーからアクセス時刻のatimeと状態の変更時刻のctimeを外します。
手元で作ったtarとActionsで作ったtarのSHA-256は一致しました。

続いて、できたtarでリリース `site` の添付ファイルを上書きします。

```yaml
- name: Upload the site to the release
  env:
    GH_TOKEN: ${{ github.token }}
  run: |
    gh release view site >/dev/null 2>&1 || gh release create site --title site \
      --notes "The site deployed by Workers Builds (make fetch-site)." --latest=false
    gh release upload site build/dist.tar.zst --clobber
```

ワークフローの権限は `contents: write` で、Actionsが自動で発行する `GITHUB_TOKEN` だけで足ります。

## 2. SHA-256をmasterにcommitする

続けて、 `SITE_SHA256` をmasterにcommitしてpushします。
tarの中身が前回と同じなら `SITE_SHA256` も変わらないので、commitは作りません。
次のステップは、POファイルの扱いを省いて抜粋したものです。

```yaml
- name: Commit the catalogs and the checksum of the site
  run: |
    git config user.name "github-actions[bot]"
    git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
    git add EMACS_REVISION SITE_SHA256 po/*.ja.po
    if ! git diff --cached --quiet; then
      git commit -m "Update the site for Emacs $(cut -c1-10 EMACS_REVISION)"
      git push
    fi
```

## 3. Workers Buildsがtarを取ってデプロイする

Workers Buildsで、Worker `emacs-doc` をGitHubのmasterにつないでいます。
build commandは `make fetch-site` 、deploy commandは `npx wrangler deploy` です。

```makefile
SITE_URL := https://github.com/conao3/emacs-doc/releases/download/site/dist.tar.zst

fetch-site:
        mkdir -p build
        curl -fsSL --retry 3 -o $(SITE_ARCHIVE) $(SITE_URL)
        echo "$$(cat SITE_SHA256)  $(SITE_ARCHIVE)" | sha256sum --check
        rm -rf $(DIST_DIR)
        mkdir $(DIST_DIR)
        tar --zstd -xf $(SITE_ARCHIVE) -C $(DIST_DIR)
```

リポジトリがpublicなので、Releaseの添付ファイルは認証なしのcurlで取れます。
取ったtarを `sha256sum --check` で `SITE_SHA256` と照合し、 `dist/` に展開します。
curl、sha256sum、tar、zstdはどれもビルドイメージに入っているので、build commandはWorkers Buildsの環境で動きます。

deploy commandでは、 `wrangler.jsonc` の `assets.directory` が `./dist` なので、 `npx wrangler deploy` が `dist/` をWorkerの静的アセットとして上げます。
デプロイが終わると、GitHubのcommitに `Workers Builds: emacs-doc` のcheck runが付きます。

# この構成の性質

-   CloudflareのAPIトークンをどこにも置かず、GitHubのSecretsは空です。Actionsが使うのは `GITHUB_TOKEN` だけです。
-   生成物をgitの履歴に入れません。サイトは2,240ファイルで、tarにすると49 MB、zstdで圧縮すると4.7 MBです。サイトについて履歴に入るのは、64文字のSHA-256だけです。
-   tarは同じ内容のファイルから同じバイト列になるので、サイトの中身が変わらなければ `SITE_SHA256` も変わりません。そのときはActionsがcommitを作らないので、デプロイも走りません。
-   READMEの修正など、 `SITE_SHA256` を変えないpushでもWorkers Buildsは動きます。このときは同じtarを取って、同じサイトをデプロイし直します。
-   `SITE_SHA256` とReleaseのtarが食い違うと、 `sha256sum --check` が失敗してbuild commandも失敗します。このときdeploy commandは実行されないので、公開中のサイトは前のままです。公開されるのは `SITE_SHA256` と一致したtarだけです。

一方で、この構成では古いcommitからデプロイし直せません。
Releaseのtarは常に最新の1つだけなので、古いcommitの `SITE_SHA256` は今のtarと合わず、ビルドが失敗します。
同じ理由で、Actionsがtarを上げてから `SITE_SHA256` をpushするまでの間に別のpushがあると、そのビルドも失敗します。
以前の版に戻すには、その版の状態でサイトを作り直して上げるしかありません。

# 似た構成の実例

トークンを置かずにActionsの生成物を公開する実例は、GitHub Pagesに多くあります。
`actions/upload-pages-artifact` で生成物をartifactに上げ、 `actions/deploy-pages` が `id-token: write` のOIDCで公開する構成です。
po4aで訳したFreeBSDの文書を公開する[fierceX/freebsd-doc-cn](https://github.com/fierceX/freebsd-doc-cn)や、makeinfoでマニュアルを作る[r0man/beads.el](https://github.com/r0man/beads.el)がこの形です。

Releaseの添付ファイルをWorkers Buildsが取ってデプロイする構成の実例は、GitHubのコード検索とWeb検索では見つかりませんでした。

# おわりに

Workers Buildsの環境でビルドできないサイトでも、ビルドをActionsに任せ、生成物をpublicのReleaseに置けば、トークンを持たずにWorkersへデプロイできます。
Workers BuildsはReleaseのtarをmasterの `SITE_SHA256` と照合するので、公開されるのは一致したtarだけです。
Workersの静的アセットで配りたいけれど、CloudflareのトークンをGitHubに置きたくないときに使えます。
