ローカルCI/CDパイプライン構築【act + lefthook + mise 完全ガイド】
コードの変更をプッシュするまで、CI(継続的インテグレーション)の実行結果が分からない。この状況にストレスを感じた経験はないでしょうか。CIの実行には時間がかかり、わずかな変更の確認でも待機が必要です。
本番環境のCI/CDパイプラインは、開発者の手元から遠い場所にあります。そのため、ローカル環境とCI環境との差異に悩まされることも少なくありません。「私の環境では動くのに、CIではエラーになる」といった問題は、開発効率を大きく低下させます。
しかし、もしGitHubへのプッシュなしで、本番環境とほぼ同等のCIをローカルで回せたらどうでしょう。開発者はコードをコミットする前に、品質チェックを完了できます。フィードバックサイクルが短縮され、手戻りが大幅に減少するはずです。
この記事では、act、lefthook、mise という3つのツールを組み合わせます。これらを使って、ローカルで本番同等のCI/CDパイプラインを構築する方法を解説します。開発体験を向上させ、より質の高いソフトウェア開発を目指しましょう。
ローカルCI/CDパイプラインが最強な理由
act、lefthook、mise の組み合わせは、ローカル開発環境を劇的に強化します。GitHubへのプッシュなしでCI/CDパイプラインを回せるためです。この連携は、開発の初期段階で問題を早期発見します。結果として、手戻りを最小限に抑え、開発効率を飛躍的に向上させます。
具体的には、以下の相乗効果が期待できます。
- 高速フィードバック:
lefthookがコミット前にコード品質をチェックします。さらに、actがGitHub Actionsワークフローをローカルで実行します。これにより、変更のたびにCIを待つ必要がなくなります。 - 環境の統一:
miseは開発ツールや環境変数をプロジェクトごとに管理します。ローカル環境とCI環境の差異をなくし、「私の環境では動くのに」問題を解決します。 - 本番同等の検証:
actはGitHub ActionsのワークフローをDockerコンテナ内で実行します。本番CI環境に近い条件でテストできるため、信頼性の高い検証が可能です。
これらのツールを組み合わせることで、開発者は安心してコードを書けます。品質の高いコードを迅速に提供できるようになるでしょう。
各ツールの役割と担当領域
ローカルCI/CDパイプラインを構築する上で、各ツールには明確な役割があります。
act: GitHub Actionsのローカル実行エンジン
act は、GitHub Actionsのワークフローをローカル環境で実行するツールです。GitHub Actionsは、コードのテストやビルド、デプロイなどの自動化を行うための機能です。通常、GitHubにコードをプッシュした際に実行されます。
act を使うと、GitHubにプッシュする手間を省き、ローカルでワークフローを試せます。Dockerを利用してGitHub Actionsのランナー環境を再現します。これにより、本番環境に近い条件で動作確認が可能です。高速なフィードバックループを実現し、GitHub Actionsのワークフロー開発を効率化します。
lefthook: Gitフックの強力なマネージャー
lefthook は、Gitフックを管理するためのツールです。Gitフックとは、git commit や git push といった特定のGit操作の前後で自動的に実行されるスクリプトを指します。例えば、コミット前にコードのフォーマットをチェックしたり、テストを実行したりできます。
lefthook は設定ファイルをYAML形式で記述します。複数のフックを並列で実行する機能も備えています。これにより、大規模なプロジェクトでも高速なチェックが可能です。コミット前に品質問題を早期に発見し、開発ワークフローに自動的な品質ゲートを設ける役割を担います。
mise: 開発環境の統一と管理
mise は、開発ツール、環境変数、タスクを一元的に管理するCLIツールです。プロジェクトごとに異なるバージョンのNode.jsやPython、Terraformなどを簡単に切り替えられます。これにより、複数のプロジェクトを並行して開発する際に発生するバージョン競合の問題を解決します。
mise は mise.toml という設定ファイルを使用します。このファイルにプロジェクトに必要なツールとバージョン、環境変数、カスタムタスクを定義します。開発環境のセットアップを自動化し、チームメンバー間で一貫した開発環境を維持するのに役立ちます。CI環境においても、mise を使うことでローカルと全く同じツールバージョンを保証できます。
実際のワークフロー:ローカルCI/CDのステップバイステップ
ここでは、mise、lefthook、act を連携させたローカルCI/CDパイプラインの構築手順を解説します。
1. 前提条件の確認
まず、以下のツールがシステムにインストールされていることを確認してください。
- Git
- Docker (actが動作するために必要です)
2. miseのインストールと初期設定
mise をインストールし、プロジェクトに必要なツールを定義します。
# miseをインストール
curl https://mise.run | sh
# miseのPATHを通す(シェルの設定ファイルに追記)
# 例: ~/.zshrc または ~/.bashrc に以下を追加
# export PATH="$HOME/.local/bin:$PATH"
# source ~/.zshrc # または source ~/.bashrc
# プロジェクトディレクトリを作成し移動
mkdir my-local-ci-project
cd my-local-ci-project
# miseを初期化し、Node.jsとpnpmを導入する例
mise use -g node@20
mise use -g pnpm@latest
# mise.tomlを作成し、プロジェクト固有のツールとタスクを定義
# このファイルは後ほど詳しく解説します
3. lefthookのインストールと設定
lefthook をインストールし、Gitフックを設定します。
# lefthookをインストール
# Goがインストールされている場合
go install github.com/evilmartians/lefthook/v2@v2.1.9
# Node.jsプロジェクトの場合
# npm install lefthook --save-dev
# プロジェクトにlefthookをインストール
# これにより、.git/hooks ディレクトリにlefthookが設定されます
lefthook install
4. actのインストール
act をインストールします。
# Homebrewを使用する場合(macOS/Linux)
brew install act
# その他のインストール方法については公式ドキュメントを参照
# https://github.com/nektos/act#installation
5. GitHub Actionsワークフローの作成
act でローカル実行するためのGitHub Actionsワークフローを作成します。
例として、build と lint のジョブを持つ ci.yml を作成します。
mkdir -p .github/workflows
touch .github/workflows/ci.yml
# .github/workflows/ci.yml の内容は後ほど詳しく解説します
6. 設定ファイルの作成と連携
次に、mise.toml と lefthook.yml を作成し、各ツールを連携させます。
mise.toml の例
プロジェクトに必要なツールバージョンと、カスタムタスクを定義します。
ここで定義したタスクは、mise run <task名> で実行できます。
[tools]
node = "20"
pnpm = "latest"
[tasks]
# pnpm installを実行するタスク
install = "pnpm install"
# lintを実行するタスク
lint = "pnpm lint" # 例えば、package.jsonに "lint": "eslint ." がある場合
# ビルドを実行するタスク
build = "pnpm build" # 例えば、package.jsonに "build": "vite build" がある場合
# テストを実行するタスク
test = "pnpm test" # 例えば、package.jsonに "test": "vitest" がある場合
.github/workflows/ci.yml の例
GitHub Actionsのワークフローを定義します。act はこのファイルを読み込んでローカル実行します。mise-action を使うことで、GitHub Actions上でも mise で定義した環境を再現できます。
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup mise
uses: jdxcode/mise-action@v2
- name: Install dependencies
run: mise run install
- name: Run build
run: mise run build
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup mise
uses: jdxcode/mise-action@v2
- name: Install dependencies
run: mise run install
- name: Run lint
run: mise run lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup mise
uses: jdxcode/mise-action@v2
- name: Install dependencies
run: mise run install
- name: Run tests
run: mise run test
lefthook.yml の例
lefthook の設定ファイルで、pre-commit フックを定義します。
ここで act コマンドを呼び出し、特定のGitHub Actionsジョブをローカルで実行させます。
pre-commit:
# 並列実行を有効にする
parallel: true
commands:
# miseで定義したlintタスクを実行
mise-lint:
run: mise run lint
# コミット対象ファイルのみをlintする場合
# files: git diff --cached --name-only --diff-filter=ACM | grep '\.ts$'
# actを使ってGitHub Actionsのlintジョブをローカルで実行
github-actions-lint:
# act -j lint は、ci.ymlで定義された 'lint' ジョブのみを実行します
# --rm は実行後にコンテナを削除します
# --use-docker はDockerコンテナを使用することを示します
run: act -j lint --rm --use-docker
# actを使ってGitHub Actionsのtestジョブをローカルで実行
github-actions-test:
run: act -j test --rm --use-docker
7. ローカルCI/CDの実行
これで準備が整いました。コードを修正し、コミットしてみましょう。
# 例: package.jsonを作成
pnpm init -y
# 例: eslintとvitestをインストール
pnpm add -D eslint vitest
# package.jsonにlintとtestスクリプトを追加
# "scripts": {
# "lint": "eslint .",
# "test": "vitest"
# }
# 適当なコードファイルを作成
echo "console.log('Hello, local CI!');" > index.js
# Gitにステージング
git add .
# コミットを実行
# この時点でlefthookが起動し、設定されたコマンドを実行します
git commit -m "feat: setup local CI pipeline"
git commit コマンドを実行すると、lefthook が mise-lint、github-actions-lint、github-actions-test の各コマンドを並列で実行します。これにより、コードがGitHubにプッシュされる前に、ローカルでコード品質チェックとCIテストが完了します。
設定ファイル・dotfilesの例
前述のワークフローで使用した設定ファイルを改めて確認します。これらをプロジェクトのルートディレクトリに配置することで、ローカルCI/CDパイプラインが機能します。
mise.toml
このファイルは、プロジェクトで使う開発ツールとタスクを定義します。
# プロジェクトで使うツールとそのバージョン
# 例えばNode.js 20系とpnpmの最新版を指定
[tools]
node = "20"
pnpm = "latest"
# プロジェクト固有のタスク定義
# これらのタスクは 'mise run <task名>' で実行可能
[tasks]
# 依存関係のインストール
install = "pnpm install"
# コードのlint実行
lint = "pnpm lint"
# プロジェクトのビルド
build = "pnpm build"
# テスト実行
test = "pnpm test"
.github/workflows/ci.yml
GitHub Actionsのワークフロー定義です。act がこのファイルを読み込みます。
name: CI # ワークフロー名
# ワークフローが実行されるトリガー
on:
push: # pushイベントで実行
branches:
- main # mainブランチへのpush
pull_request: # pull_requestイベントで実行
branches:
- main # mainブランチへのpull request
# 実行するジョブの定義
jobs:
build: # ビルドジョブ
runs-on: ubuntu-latest # 実行環境
steps:
- uses: actions/checkout@v4 # リポジトリをチェックアウト
- name: Setup mise # miseのセットアップ
uses: jdxcode/mise-action@v2 # mise-actionを使ってmiseを導入
- name: Install dependencies # 依存関係のインストール
run: mise run install # miseで定義したinstallタスクを実行
- name: Run build # ビルド実行
run: mise run build # miseで定義したbuildタスクを実行
lint: # lintジョブ
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup mise
uses: jdxcode/mise-action@v2
- name: Install dependencies
run: mise run install
- name: Run lint # lint実行
run: mise run lint # miseで定義したlintタスクを実行
test: # テストジョブ
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup mise
uses: jdxcode/mise-action@v2
- name: Install dependencies
run: mise run install
- name: Run tests # テスト実行
run: mise run test # miseで定義したtestタスクを実行
lefthook.yml
Gitフックの動作を定義します。pre-commit フックで mise と act を連携させます。
# pre-commitフックの設定
pre-commit:
# コマンドを並列で実行
parallel: true
commands:
# miseで定義したlintタスクを実行
# コミット対象のファイルに対してのみ実行したい場合、filesオプションを追加
mise-lint:
run: mise run lint
# files: git diff --cached --name-only --diff-filter=ACM | grep '\.ts$'
# actを使ってGitHub Actionsのlintジョブをローカルで実行
# -j lint は ci.yml の lint ジョブのみを実行
# --rm は実行後にDockerコンテナを削除
# --use-docker はDockerコンテナで実行
github-actions-lint:
run: act -j lint --rm --use-docker
# actを使ってGitHub Actionsのtestジョブをローカルで実行
github-actions-test:
run: act -j test --rm --use-docker
これらの設定ファイルを適切に配置し、それぞれのツールをインストールすることで、ローカルでのCI/CDパイプラインが構築されます。
よくあるトラブルと解決法
ローカルCI/CDパイプラインの構築中に遭遇しやすいトラブルとその解決策を説明します。
1. act がDockerを見つけられない、または権限エラー
act はDockerコンテナ内でワークフローを実行します。Dockerデーモンが起動していない、またはユーザーにDockerソケットへのアクセス権がない場合に発生します。
解決法:
- Docker DesktopなどのDockerデーモンが起動しているか確認します。
- Linux環境では、現在のユーザーを
dockerグループに追加します。sudo groupadd docker # dockerグループが存在しない場合 sudo usermod -aG docker $USER newgrp docker # グループ変更を即時適用(または再ログイン)
2. lefthook が実行されない
git commit 時に lefthook が動作しない場合、インストールが不完全な可能性があります。
解決法:
- プロジェクトのルートディレクトリで
lefthook installを実行したか確認します。これにより、.git/hooksディレクトリにフックスクリプトが配置されます。 lefthook.ymlの構文エラーがないか確認します。YAMLのインデントやキーが正しいかチェックしましょう。- Gitフックが何らかの理由でスキップされていないか確認します。例えば
git commit --no-verifyを使っていないか。
3. mise がツールをインストールできない、または認識しない
mise が必要なツールバージョンを見つけられない、またはコマンドが実行できない場合に発生します。
解決法:
mise useコマンドでツールが正しく指定されているか確認します。例えばmise use node@20。- ネットワーク接続に問題がないか確認します。ツールは通常、外部からダウンロードされます。
mise reshimを実行して、実行パス(PATH)を更新します。mise installを実行して、mise.tomlに定義されたツールが全てインストールされているか確認します。
4. GitHub Actionsとローカル(act)で結果が異なる
act はGitHub Actionsの環境を忠実に再現しますが、完全に同一ではありません。特に環境変数やシークレットの扱いで差異が生じることがあります。
解決法:
- GitHub Actionsのシークレットは
actでは自動的に利用できません。actのsecretsオプション (-s KEY=VALUE) や.secretsファイルを使ってローカルで設定します。 - 環境変数がローカルとCIで異なる場合、
actのenvオプション (-e KEY=VALUE) や.envファイルで調整します。 actの--verboseオプションを使って詳細なログを確認し、差異の原因を探ります。- Dockerイメージの違いも考慮します。
actはデフォルトでnektos/act-environments-ubuntu:18.04などのイメージを使います。ワークフローで指定されたusesアクションがローカル環境で利用可能か確認します。
5. lefthook でファイルが見つからないエラー
lefthook のコマンド内で、期待するファイルが渡されない場合に発生します。特に pre-commit フックでは、ステージングされたファイルのみが対象となることが多いです。
解決法:
lefthook.ymlのコマンド定義にfiles:オプションを使用している場合、そのパターンが正しいか確認します。- 例えば、
git diff --cached --name-only --diff-filter=ACMのようなコマンドで、ステージングされたファイルのリストを明示的に取得し、パイプで後続のコマンドに渡すようにします。 lefthookのrunコマンドが、引数として受け取るファイルリストを適切に処理できるか確認します。
これらのトラブルシューティングを通じて、スムーズなローカルCI/CDパイプラインの運用を目指しましょう。
今日からできる実行プラン
ローカルCI/CDパイプラインの導入は、開発効率を大きく改善します。今日から実践できる3つのステップを紹介します。
ステップ1: 各ツールのインストール
まずは、mise、lefthook、act の3つのツールを開発環境に導入します。
# miseのインストール
curl https://mise.run | sh
# シェルの設定ファイルにPATHを追加し、再読み込み
# export PATH="$HOME/.local/bin:$PATH"
# lefthookのインストール (Goがインストールされている場合)
go install github.com/evilmartians/lefthook/v2@v2.1.9
# または Node.jsプロジェクトの場合
# npm install lefthook --save-dev
# actのインストール (macOS/LinuxでHomebrewを使用)
brew install act
これらのコマンドを実行し、各ツールのバージョンを確認して、正しくインストールされたことを確かめます。
mise --version
lefthook version
act --version
ステップ2: プロジェクトへの導入と基本設定
次に、既存または新規のプロジェクトにこれらのツールを導入します。
- プロジェクトの初期化: Gitリポジトリがまだない場合は作成します。
mkdir my-project-name cd my-project-name git init mise.tomlの作成: プロジェクトで使うツール(例: Node.js, Python)と、簡単なタスクを定義します。# mise.toml を作成 touch mise.toml # 例: Node.js 20系とpnpmを定義 mise use -g node@20 mise use -g pnpm@latest # mise.toml にタスクを追加(手動またはエディタで) # [tasks] # lint = "pnpm lint" # test = "pnpm test"lefthook.ymlの作成:pre-commitフックでmise run lintを実行する設定を追加します。# lefthook.yml を作成 touch lefthook.yml # lefthook.yml に以下の内容を記述(手動またはエディタで) # pre-commit: # commands: # mise-lint: # run: mise run lintlefthookのインストール: プロジェクトのGitフックにlefthookを設定します。lefthook install- GitHub Actionsワークフローの作成: 簡単なCIワークフロー (
.github/workflows/ci.yml) を作成します。最初はlintジョブだけでも構いません。mkdir -p .github/workflows touch .github/workflows/ci.yml # ci.yml に lint ジョブを定義(例を参考に)
ステップ3: 既存ワークフローの移行と拡張
最後に、既存のCIジョブを act でローカル実行できるように調整し、lefthook で自動化します。
lefthook.ymlの更新:lefthook.ymlにactを使ったGitHub Actionsジョブの実行を追加します。# lefthook.yml に以下を追加 # pre-commit: # commands: # github-actions-lint: # run: act -j lint --rm --use-dockergit commitで動作確認: コードを修正し、git add . && git commit -m "feat: setup local CI"を実行します。lefthookとactが連携して動作することを確認しましょう。- CIジョブの拡充:
mise.tomlと.github/workflows/ci.ymlにbuildやtestジョブを追加します。lefthook.ymlも更新し、これらのジョブもpre-commitで実行されるように設定します。
この3ステップで、あなたの開発環境は強力なローカルCI/CDパイプラインを手に入れます。高速なフィードバックと本番同等の検証により、開発効率とコード品質が飛躍的に向上するでしょう。