act を DonD で動かす
GitHub Actions をローカルでテストできる **act** は、ワークフローの開発を効率化する強力なツールです。しかし、開発環境ごとにactをインストールするのは手間がかかります。
はじめに#
GitHub Actions を毎回 GitHub 側で動作確認をするようにしていると、ghコマンドである程度簡略化できたとしてもテストが重いなーと感じる。 調べると act というのがあってローカルでテストするためのデファクトスタンダードのツールのようなのでこちらをちょっと素振り。
act とは#
主な利点:
- 開発サイクルの高速化: push せずにワークフローをテスト
- コスト削減: GitHub Actions の無料枠を消費しない
- オフライン開発: インターネット接続なしでも動作確認可能
- デバッグの容易さ: ローカルで詳細なログを確認できる
と言われても開発環境ごとにCI開発を入れるのもしんどいし、個別にactをインストールするのも手間がかかってしまうので とりあえず今回は、Docker on Docker (DonD) で act 環境をコンテナ化し、どの環境でも簡単に使えるようにするのがよいかなと思ったので対応します
なぜコンテナ化するのか#
基本的に Mac が開発機ですが、Windowsマシンもあり、WSLを使うこともあるので、とりあえずコンテナ化するモチベーションは以下。
- 環境の統一: チームメンバー全員が同じ環境で開発
- 簡単な配布: Dockerさえあれば動作する
- クリーンな環境: ホストシステムを汚さない
- 再現性の確保: Dockerfileで環境を完全に定義
DonD or DinD: アーキテクチャの選択#
act はワークフロー実行時に Docker コンテナを起動する形式になるが、コンテナ内から Docker を使う方法は2つある
Docker in Docker (DinD)#
コンテナ内で独立した Docker デーモンを起動する方式。
メリット:
- 完全に独立した環境
- ホストの Docker に影響を与えない
デメリット:
- 二重の仮想化によるパフォーマンス低下
- リソース消費が大きい
- 特権モード (
--privileged) が必要でセキュリティリスクが高い
Docker on Docker (DonD) - 今回の選択#
コンテナ内からホストの Docker デーモンを利用する方式。
メリット:
- パフォーマンスが良い: ホストの Docker を直接使用
- リソース効率が高い: 二重仮想化なし
- イメージの共有: ホストとコンテナでイメージを共有できる
デメリット:
- Docker socket の共有による潜在的なセキュリティリスク
- ホストの Docker に依存
今回の判断基準#
今回は以下の理由から DonD を選択。 CI/CDだとDonDが多い気はします。GitLab Runnerを利用しているときもこちらの構成を使っていたことがあるので慣れているのもあります。
- ターゲット環境が Linux: 安定した動作が期待できる
- 開発環境での使用: セキュリティリスクは許容範囲
- パフォーマンス重視: ワークフローの実行速度を優先
- シンプルな構成: 設定が簡単で保守しやすい
Dockerfile の作成#
それではまず act を動かすための Dockerfile を作成します。
FROM alpine:latest
RUN apk add --no-cache \ docker-cli \ curl \ bash \ git \ ca-certificates
RUN curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | bash
WORKDIR /workspace
CMD ["/bin/bash"]Dockerfile の解説#
ベースイメージ:
FROM alpine:latest- Alpine Linux を選択: 軽量(約5MB)で高速
- セキュリティアップデートが早い
- パッケージマネージャー(apk)が使いやすい
必要なパッケージのインストール:
RUN apk add --no-cache \ docker-cli \ # ホストのDockerデーモンと通信するCLI curl \ # actのインストールスクリプト取得用 bash \ # actとワークフローの実行に必要 git \ # リポジトリ操作とactions/checkout用 ca-certificates # HTTPS通信の証明書検証用--no-cache: キャッシュを残さずイメージサイズを削減
act のインストール:
RUN curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | bash- 公式のインストールスクリプトを使用
/usr/local/bin/actにバイナリが配置される- 最新版が自動的にインストールされる
作業ディレクトリの設定:
WORKDIR /workspace- リポジトリがマウントされるディレクトリ
- compose.yamlの設定と一致させる
デフォルトコマンド:
CMD ["/bin/bash"]- コンテナ起動時にbashシェルを起動
- インタラクティブな操作を可能にする
Docker Compose 設定の作成#
次に compose.yaml を作成します。
services: act: build: . container_name: act-runner volumes: - /var/run/docker.sock:/var/run/docker.sock - .:/workspace working_dir: /workspace stdin_open: true tty: truecompose.yaml の解説#
サービスの基本設定:
services: act: build: . # カレントディレクトリのDockerfileを使用 container_name: act-runner # コンテナに固定名を付与(識別しやすくする)最も重要な設定 - Docker socket の共有:
volumes: - /var/run/docker.sock:/var/run/docker.sockDonD なのでこうなります。ホストの Docker socket をコンテナにマウントすることで、コンテナ内から ホストの Docker デーモン に直接アクセスできます。
リポジトリのマウント:
- .:/workspace- カレントディレクトリ(リポジトリルート)をコンテナの
/workspaceにマウント - ワークフローファイルやソースコードにアクセスできるようにする
作業ディレクトリ:
working_dir: /workspace- コンテナ起動時のカレントディレクトリを指定
- Dockerfileの
WORKDIRと一致
インタラクティブモード:
stdin_open: true # 標準入力を開いたままにする (-i フラグ相当)tty: true # 疑似TTYを割り当てる (-t フラグ相当)- シェルの対話的な操作を可能にする
docker compose execでコンテナにアクセスする際に必要
これで DonD 環境の準備が完了しました。
動作確認#
実際に環境を起動してテストしてみます。
コンテナの起動#
docker compose up -dバックグラウンドでコンテナを起動します。初回は Dockerfile からイメージをビルドするため数分かかります。
コンテナへのアクセス#
docker compose exec act bashコンテナ内のシェルに入ります:
c813419d3e2a:/workspace#act のイメージサイズ選択#
初回実行時に act が使用するベースイメージを選択するよう求められます:
$ docker compose exec act bash
c813419d3e2a:/workspace# act -nINFO[0000] Using docker host 'unix:///var/run/docker.sock', and daemon socket 'unix:///var/run/docker.sock'? Please choose the default image you want to use with act: - Large size image: ca. 17GB download + 53.1GB storage, you will need 75GB of free disk space, snapshots of GitHub Hosted Runners without snap and pulled docker images - Medium size image: ~500MB, includes only necessary tools to bootstrap actions and aims to be compatible with most actions - Micro size image: <200MB, contains only NodeJS required to bootstrap actions, doesn't work with all actions
Default image and other options can be changed manually in /root/.config/act/actrc (please refer to https://nektosact.com/usage/index.html?highlight=configur#configuration-file for additional information about file structure) [Use arrows to move, type to filter, ? for more help] Large> Medium Microイメージサイズの選択基準:
| サイズ | ダウンロード | ディスク使用 | 特徴 | 推奨用途 |
|---|---|---|---|---|
| Large | 17GB | 53GB | GitHub Hosted Runners とほぼ同じ環境 | 本番環境との完全な互換性が必要な場合 |
| Medium | 500MB | 数GB | 大半のアクションが動作する | 一般的な開発に推奨 |
| Micro | <200MB | 最小 | Node.js のみ | シンプルなワークフローのみ |
今回は Medium を選択します。ほとんどのワークフローで十分に動作します。(使っているマシンに空きディスクがあんまりなかったとは恥ずかしくて言えない)
テストワークフローの準備#
.github/workflows/test.yml を用意します:
name: Test Workflow
on: push: branches: [ main ] pull_request: branches: [ main ]
jobs: test: runs-on: ubuntu-latest
steps: - name: Checkout code uses: actions/checkout@v4
- name: Run a simple test run: | echo "Hello from GitHub Actions!" echo "Running on: $(uname -a)" echo "Current directory: $(pwd)"
- name: List files run: ls -laこのワークフローは以下をテストします:
- リポジトリのチェックアウト (
actions/checkout@v4) - シェルコマンドの実行
- ファイルシステムへのアクセス
Dry-run での実行計画確認#
まずは dry-run モード (-n) で実行計画を確認します:
c813419d3e2a:/workspace# act -n -W .github/workflows/test.ymlINFO[0000] Using docker host 'unix:///var/run/docker.sock', and daemon socket 'unix:///var/run/docker.sock'*DRYRUN* [Test Workflow/test] ⭐ Run Set up job*DRYRUN* [Test Workflow/test] 🚀 Start image=catthehacker/ubuntu:act-latest*DRYRUN* [Test Workflow/test] 🐳 docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true*DRYRUN* [Test Workflow/test] 🐳 docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"*DRYRUN* [Test Workflow/test] 🐳 docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"*DRYRUN* [Test Workflow/test] ✅ Success - Set up job*DRYRUN* [Test Workflow/test] ⭐ Run Main Checkout code*DRYRUN* [Test Workflow/test] ✅ Success - Main Checkout code [10.669541ms]*DRYRUN* [Test Workflow/test] ⭐ Run Main Run a simple test*DRYRUN* [Test Workflow/test] ✅ Success - Main Run a simple test [28.75825ms]*DRYRUN* [Test Workflow/test] ⭐ Run Main List files*DRYRUN* [Test Workflow/test] ✅ Success - Main List files [28.826916ms]*DRYRUN* [Test Workflow/test] ⭐ Run Complete job*DRYRUN* [Test Workflow/test] Cleaning up container for job test*DRYRUN* [Test Workflow/test] ✅ Success - Complete job*DRYRUN* [Test Workflow/test] 🏁 Job succeededDry-run の出力から分かること:
- Docker 接続確認:
Using docker host 'unix:///var/run/docker.sock'- DonD が正しく動作している - 実行計画: 各ステップが順番に表示される
- 使用イメージ:
catthehacker/ubuntu:act-latestが使われる - エラーの事前検出: 実際に実行する前に問題を発見できる
すべてのステップが ✅ Success となり、🏁 Job succeeded と表示されました。問題なさそうです。
実際のワークフロー実行#
次に -n フラグを外して実際に実行します:
c813419d3e2a:/workspace# act -W .github/workflows/test.ymlINFO[0000] Using docker host 'unix:///var/run/docker.sock', and daemon socket 'unix:///var/run/docker.sock'[Test Workflow/test] ⭐ Run Set up job[Test Workflow/test] 🚀 Start image=catthehacker/ubuntu:act-latest[Test Workflow/test] 🐳 docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true[Test Workflow/test] 🐳 docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"[Test Workflow/test] 🐳 docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"[Test Workflow/test] 🐳 docker exec cmd=[node --no-warnings -e console.log(process.execPath)] user= workdir=[Test Workflow/test] ✅ Success - Set up job[Test Workflow/test] ⭐ Run Main Checkout code[Test Workflow/test] 🐳 docker cp src=/workspace/. dst=/workspace[Test Workflow/test] ✅ Success - Main Checkout code [151.338375ms][Test Workflow/test] ⭐ Run Main Run a simple test[Test Workflow/test] 🐳 docker exec cmd=[bash -e /var/run/act/workflow/1] user= workdir=| Hello from GitHub Actions!| Running on: Linux docker-desktop 6.10.14-linuxkit #1 SMP Sat May 17 08:28:57 UTC 2025 aarch64 aarch64 aarch64 GNU/Linux| Current directory: /workspace[Test Workflow/test] ✅ Success - Main Run a simple test [137.776875ms][Test Workflow/test] ⭐ Run Main List files[Test Workflow/test] 🐳 docker exec cmd=[bash -e /var/run/act/workflow/2] user= workdir=| total 52| drwxr-xr-x 6 root root 4096 Oct 5 16:26 .| drwxr-xr-x 1 root root 4096 Oct 5 16:26 ..| drwxr-xr-x 7 root root 4096 Oct 5 16:26 .git| drwxr-xr-x 3 root root 4096 Oct 5 16:26 .github| -rw-r--r-- 1 root root 467 Oct 5 15:51 .gitignore| -rw-r--r-- 1 root root 377 Oct 5 15:45 Dockerfile| -rw-r--r-- 1 root root 5055 Oct 5 16:22 README.md| -rw-r--r-- 1 root root 933 Oct 5 15:47 build.js| -rw-r--r-- 1 root root 383 Oct 5 15:56 docker-compose.yml| -rw-r--r-- 1 root root 349 Oct 5 15:47 package.json| drwxr-xr-x 2 root root 4096 Oct 5 16:26 src| drwxr-xr-x 2 root root 4096 Oct 5 16:26 terraform[Test Workflow/test] ✅ Success - Main List files [133.259625ms][Test Workflow/test] ⭐ Run Complete job[Test Workflow/test] Cleaning up container for job test[Test Workflow/test] ✅ Success - Complete job[Test Workflow/test] 🏁 Job succeeded実行結果のポイント:
- イメージのダウンロード: 初回は
docker pullでイメージを取得(Medium: 約500MB) - コンテナの作成と起動: ワークフロー実行用の一時コンテナが起動
- コードのチェックアウト:
docker cpでリポジトリがコンテナにコピー - コマンドの実行: 各ステップのコマンドが実際に実行される
- 出力の表示:
|プレフィックスで実行結果が表示される| Hello from GitHub Actions!| Running on: Linux docker-desktop 6.10.14-linuxkit ... - クリーンアップ: ジョブ完了後、一時コンテナが自動削除される
すべて成功し、🏁 Job succeeded が表示されました!
まとめ#
構築した環境の利点#
どこでも動作: Docker さえあればOK
高速: DonD によるオーバーヘッド最小化
使いやすい: docker compose exec act bash で即座にアクセス
チーム共有可能: Dockerfile と compose.yaml を共有するだけ
できるようになったこと#
- GitHub Actions ワークフローのローカルテスト
- Dry-run による事前検証
- push 前のワークフロー確認
- CI/CD パイプラインの高速な開発サイクル
次のステップ#
この基本環境を使って、以下のような活用ができます:
- 複数ワークフローの並列テスト:
-jオプションで特定ジョブを指定 - 環境変数やシークレットの設定:
.secretsファイルで機密情報を管理 - カスタムイベントのテスト:
pull_requestやworkflow_dispatchのシミュレート - イメージのカスタマイズ: プロジェクト固有のツールを追加