TL:DR
-
actを使うと、GitHub Actionsのワークフロー(.github/workflows/*.yml)をpushせずローカルのDockerコンテナー上で検証できる - act入門用にAIで題材アプリを生成し、テスト / lint / 認知的複雑度 / 依存監査という4ジョブ並列のシンプルなCI構成を通せるようにした
対象読者
- GitHub Actionsのワークフローを書いたことがある
- Docker Desktop(またはPodman)の基本操作ができる
- 「pushしないとCIの結果が分からない」フィードバックの遅さに困っている
本記事の執筆には生成AIを利用しています
act は、GitHub ActionsのワークフローをローカルのDockerコンテナー上でそのまま実行するCLIツールです。pushする前に「このワークフローは本当に動くか」を手元で検証できます。
- pushしなくても検証できる: ワークフローYAMLの構文ミスやステップの実行結果をローカルで確認できる
-
.actrcで環境差異を吸収: actのデフォルトrunnerイメージは軽量すぎて動かないActionがあるため、-Pオプションでラベルごとにイメージをマッピングして解決する -
軽量な確認手段もある:
act -lはDockerなしでジョブ構成だけ確認できる -
実プロジェクトで体験できる: 本記事では実際に手元で動かしている
act_sample(TODOアプリ)のCI設定を題材にする
この記事では、act のセットアップから実行、つまずきやすいポイントまでを実際に手を動かしながらひととおり体験します。
きっかけ
CIを組んだ直後、実際に手を動かしていると次のような場面に何度も遭遇しました。
- ワークフローのYAMLを少し書き換えるたびに、動作確認のためだけにpushしてGitHub Actionsの実行結果を待つ必要があった
- テスト / lint / 認知的複雑度 / 依存監査の3ジョブのうち、どれか1つだけを素早く再確認したい場面が多く、pushベースの確認サイクルでは効率が悪かった
- せっかく実際に動くCI設定がリポジトリにあるので、これを利用すれば「架空のサンプルではなく、自分たちが実際に運用しているワークフローをそのまま検証できる」ハンズオンが作れると思い立った
この「実際に動いているCI設定を、pushせずに手元で検証できるようにする」という体験を、読者にもそのままなぞってもらえるようにしたいと考えました。
Step 1: 題材プロジェクトを確認する
題材はbun + Hono + React + bun:sqliteで組んだ最小構成のTODOアプリ act_sample です。
サーバーは createTodoRepository(DB層)と createApp(Honoルーティング層)を分離したテスト容易な構成になっており、/api/* には ACCESS_TOKEN によるBasic認証がかかっています。
トップページ(/)自体は認証不要で、サーバーがHTMLに埋め込んだトークンをフロントエンドの src/client/api.ts が読み取り、/api/* への各リクエストへ自動的に Authorization ヘッダーを付与する構成になっているため、アプリを通常どおり使う分にはブラウザの認証プロンプトは表示されません。
本アプリを作成したClaude Code用プロンプトは本記事の最後に付録として掲載しています。
act_sample/
├── .actrc # actのrunnerイメージ設定
├── .github/workflows/ci.yml # GitHub Actionsワークフロー
├── biome.json # Lint/Format/認知的複雑度の設定
├── bunfig.toml # bun test のプリロード設定
├── src/
│ ├── client/
│ │ ├── App.tsx
│ │ ├── App.test.tsx # Reactコンポーネントのテスト
│ │ ├── api.ts # Authorizationヘッダー自動付与を含むfetchラッパー
│ │ └── main.tsx # クライアントエントリーポイント
│ ├── server/
│ │ ├── app.ts # Honoルーティング(テストしやすいよう分離)
│ │ ├── app.test.ts # APIエンドポイント・認証のテスト
│ │ ├── db.ts # TodoRepository(bun:sqlite)
│ │ ├── db.test.ts # リポジトリ層のテスト
│ │ └── index.tsx # エントリーポイント(ACCESS_TOKEN読み込み)
│ └── test/
│ └── happydom.ts # React Testing Library用のDOM環境
└── package.json
CIは4つの独立したジョブで構成されています。
| ジョブID | 内容 |
|---|---|
test |
bunx tsc --noEmit による型チェック + bun test によるユニット/結合テスト |
lint |
bunx biome check . - フォーマット・Lintチェック |
complexity |
bunx biome lint --only=complexity/noExcessiveCognitiveComplexity . - 認知的複雑度チェック(ジョブ名は Cognitive complexity check) |
audit |
bun audit - 依存パッケージの脆弱性監査 |
いずれも actions/checkout@v7 → oven-sh/setup-bun@v2 → actions/cache@v6(bun.lock のハッシュをキーにした依存キャッシュ)→ bun install --frozen-lockfile という共通の前段を持ち、runs-on: ubuntu-slim で並列実行されます(本ハンズオンの検証環境で bun test を実行し、3ファイル26テストすべて成功することを確認済みです)。
Step 2: actをインストールする
Windows(今回の検証環境):
winget install nektos.act
macOS:
brew install act
インストール後、バージョンを確認します。
$ act --version
act version 0.2.89
前提条件: actはDockerコンテナー上でジョブを実行するため、Docker Desktop(またはPodman)が起動している必要があります。
Step 3: .actrc でrunnerイメージをマッピングする
actはデフォルトで軽量な -slim イメージを使いますが、必要最小限のツールしか入っておらず、setup-bun のような一部のsetup系Actionが動かないことがあります。本プロジェクトのワークフローは4ジョブとも runs-on: ubuntu-slim を使っているため、.actrc でこのラベルを catthehacker/ubuntu:act-latest イメージにマッピングしています。
.actrc
-P ubuntu-latest=catthehacker/ubuntu:full-latest
-P ubuntu-slim=catthehacker/ubuntu:act-latest
-P <ラベル>=<イメージ> は「ワークフロー内の runs-on: <ラベル> をこのDockerイメージに置き換える」という指定です。
.actrc に書いておくことで、act コマンド実行時に毎回 -P を指定しなくて済みます。
act-latest は full-latest ほど巨大ではないものの、-slim の素のイメージより多くのツールを含む中間サイズのイメージで、本プロジェクトの4ジョブではこれで動作します。ubuntu-latest ラベルを使うワークフロー(本プロジェクトでは未使用)向けに、より完全な full-latest へのマッピングも合わせて用意しています。
注意: どちらのイメージも数GB単位あり、初回pullに時間がかかります。CIで使っているActionが何を要求するか分からない場合は、まず
act-latest(もしくはfull-latest)を試し、動作を確認できたら軽量イメージへの置き換えを検討するのが実践的です。
Step 4: ワークフローを確認する
.github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
name: bun test
runs-on: ubuntu-slim
steps:
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: actions/cache@v6
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
restore-keys: ${{ runner.os }}-bun-
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Type check
run: bunx tsc --noEmit
- name: Run tests
run: bun test
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
lint:
name: Biome lint & format check
runs-on: ubuntu-slim
steps:
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: actions/cache@v6
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
restore-keys: ${{ runner.os }}-bun-
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Biome check
run: bunx biome check .
complexity:
name: Cognitive complexity check
runs-on: ubuntu-slim
steps:
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: actions/cache@v6
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
restore-keys: ${{ runner.os }}-bun-
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Check cognitive complexity
run: bunx biome lint --only=complexity/noExcessiveCognitiveComplexity .
audit:
name: bun audit
runs-on: ubuntu-slim
steps:
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: actions/cache@v6
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
restore-keys: ${{ runner.os }}-bun-
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Audit dependencies
run: bun audit
test ジョブは env: ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} の形でGitHub Actionsのsecretsを参照しています。
actはデフォルトでsecretsを空文字として扱うため、--secret-file などでローカル用のsecretファイルを渡さないと bun test が認証失敗で落ちます。
本プロジェクトではリポジトリ直下に(.gitignore済みの).secrets ファイルを用意し、ACCESS_TOKEN=<値> の形式で1行記述しています(値はsample:sampleをBase64エンコードしたもの)。
実行方法はStep 6を参照してください。
complexity ジョブが参照している noExcessiveCognitiveComplexity ルールは biome.json 側で閾値を設定しています。
biome.json(抜粋)
{
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"noExcessiveCognitiveComplexity": {
"level": "error",
"options": { "maxAllowedComplexity": 15 }
}
}
}
}
}
Step 5: ジョブ一覧を表示する(Docker起動不要)
act -l はワークフローファイルをパースしてジョブ一覧を表示するだけなので、Dockerが起動していなくても実行できます。まずはこれで意図通りに認識されているか確認しましょう。
$ act -l
Stage Job ID Job name Workflow name Workflow file Events
0 test bun test CI ci.yml pull_request,push
0 lint Biome lint & format check CI ci.yml push,pull_request
0 complexity Cognitive complexity check CI ci.yml push,pull_request
0 audit bun audit CI ci.yml push,pull_request
⇧本ハンズオンの検証環境で実際に出力された結果です。4ジョブとも Stage 0 なので、依存関係なく並列実行される設定になっていることが分かります。
Step 6: ワークフローを実行する
test ジョブは secrets.ACCESS_TOKEN を参照するため、--secret-file でローカル用のsecretファイルを渡して実行します。push イベントに紐づくジョブをすべて実行するには:
act push --secret-file .secrets
特定のジョブだけ実行したい場合:
act push -j test --secret-file .secrets
act push -j lint
act push -j complexity
act push -j audit
test 以外の3ジョブはsecretsを参照しないため、--secret-file を付けなくても動作します。
本記事の執筆環境では act バイナリと .actrc の設定、act -l によるジョブ一覧表示に加えて、Podman Desktop(Docker Desktopの代替として利用可能なコンテナー管理ツール)を使った act push --secret-file .secrets の実行も確認できました。
下記のとおり、bun test / Biome lint & format check / Cognitive complexity check / bun audit の4ジョブすべてが Success - Complete job → Job succeeded のログで完了しており、.actrc のマッピング通りコンテナーが起動して各ジョブの最終的な成功まで実機で確認できています。
技術スタック選定基準
- act: GitHub Actionsのワークフローファイルをそのまま読み込んで実行できるため、CI専用の別DSLを覚える必要がない。pushせずにロジックの正しさを検証したいだけなら、まずこれを試すのが最短ルート
-
.actrcによるイメージ指定:catthehacker/ubuntu:act-latest/full-latestはGitHub-hosted runnerに近いツール構成を持つため、-slimの素のイメージで頻発する「ツールが足りず途中で落ちる」問題を避けやすい -
4ジョブ並列構成: テスト・lint・認知的複雑度・依存監査を1ジョブにまとめず分割しているため、
act push -j <job>で気になるジョブだけをピンポイントに再実行でき、ローカル検証のサイクルが速くなる
いずれも「pushせずに素早く確認できるか」を基準に選んでいる。ジョブ数が増え依存関係(needs)が複雑になってきた場合は、act -l の Stage 列で実行順序を都度確認する運用に切り替えるとよい。
ハマりポイント
-
Cannot connect to the Docker daemon: Docker Desktopが起動していない。Docker Desktopを起動してから再実行する -
setup-bun等のsetup系Actionが失敗する:-slimイメージにはツールが不足している場合がある。.actrcでact-latestやfull-latestイメージにマッピングする -
Apple Silicon (M1/M2) で
exec format error等: イメージのアーキテクチャ不一致が原因のことがある。--container-architecture linux/amd64の指定を検討する -
secretsを参照するステップで失敗する: actはデフォルトでsecretsを空として扱う。act push --secret-file .secrets等でローカル用のsecretファイルを渡す - ジョブは通るがGitHub上の実行と結果が微妙に違う: actのDockerイメージはGitHub-hosted runnerの完全な再現ではない。あくまで「ロジックの事前検証」用途と割り切るのが実践的
まとめ
act を使えば、GitHub Actionsのワークフローをpush前にローカルのDockerコンテナーで検証できます。
.actrc の -P オプションでrunnerイメージをact-latestやfull-latestにマッピングしておけば、setup系Actionが軽量イメージで動かない問題を避けやすくなります。
まずはDocker起動不要の act -l でジョブ構成を確認し、慣れてきたら act push -j <job>(secrets を参照する test ジョブは --secret-file を付けて)で気になるジョブだけをピンポイントに検証する、という流れがオススメです。
テスト・lint・複雑度チェック・依存監査のように独立したジョブに分割しておくと、actでの部分実行がしやすくなるという副次的なメリットもあります。
本格的にジョブ数が増えて needs による依存関係が複雑化してきた場合は、act -l の Stage 列で実行順序を確認しながら、必要なジョブだけを選んで実行する運用に発展させていくとよさそうです。
付録:サンプルTODOアプリact_sampleを作ったプロンプト
サンプルTODOアプリのソースコード?ではありませんが、以下のプロンプトだけで90%くらいは再現できると思います。
一部typoがありますが、原文ママです。Claude Code Sonnet5で私のシステムプロンプトのある環境でしか試していませんが、参考まで。
サンプルTODOアプリ生成プロンプト
@act_sample 以下にbun+Hono+React+sqlite3+pico.cssでシンプルなTODO WebアプリをローカルPCで動く最小構成で作成して。.npmrcで直近7日にリリースされたライブラリは読み込まないようにし、.actrcでunbuntu-slimeイメージを通常のUbuntuにマッピングしてGitHubのWorkflowでbiomeとbun auditと認知的複雑度をチェックするCIを追加して
単一の意味を持つUIは1行に収まるように美しくレイアウトして
1) 入力フォームを1行で収まるようにして。2) 各TODOはカードにして
1) UIのスタイルをpico.cssから最新のbootstrapに変更して。2) 文字フォントはNoto Sansのweight 400,700にして
サーバー側APIは`Authentication: Basic`を必須とし、sample:sample をつかったトークンで通信するようにして。アクセストークンは @.env を参照するようにして
@.env の内容は`ACCESS_TOKEN={base64化された"sample:sample"}`となるようにして
1) Jestによる自動テストを追加して。2) フロントエンド・バックエンドともに単体テストと機能テスト、結合テストを仕様を正としてテストコードを作成してすべて合格するようにコードを改善して。3) すべて合格したらデッドコードを削除するなど、リファクタリングして。4) リファクタリング後にすべてのテストに合格することを確認して
システムの利用にはBASIC認証は不要です。APIの認証用にBASIC認証が使いたいだけでした。
システムの絵文字としてtwemoji `memo2`を使用して。ダウンロードせず、CDNを利用して
