OllamaとGitHub Actionsを連携させる方法|セルフホストランナーにローカルLLMを導入してPRレビューと自動要約を実装する手順

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > ローカルLLM > OllamaとGitHub Actionsを連携させる方法|セルフホストランナーにローカルLLMを導入してPRレビューと自動要約を実装する手順
「GitHub ActionsのコードレビューにクラウドAIを使うとAPIコストが積み上がる…」
「PRの差分をクラウドに送るのはセキュリティポリシー上NG、でも自動化もしたい」
そんな悩みを抱えるLinuxサーバー管理者・DevOpsエンジニアは多いはずです。この記事では、GitHub Actionsのセルフホストランナーを立てたLinuxサーバーにOllamaを同居させ、PRレビューコメントの自動投稿とコミットメッセージ自動生成ワークフローを実装する手順を解説します。ソースコードをクラウドAIに送ることなく、社内サーバーだけでコードレビューを自動化するための設定をステップごとに示します。

OllamaのLinuxへの基本的なインストールは完了済みであることを前提にしています。まだの方は先にUbuntu ServerでローカルLLMを構築する方法で環境を整えてください。

この記事のポイント

・セルフホストランナーとOllamaを同一Linuxサーバーに同居させ、PRレビューをローカルLLMで実行する
・ワークフローYAMLでgit diff取得→ollama run呼び出し→gh pr comment投稿の流れを実装する
・Gemma3:9b-instruct-q4_0がコードレビュー用途で速度・品質のバランスが取りやすい
・GITHUB_TOKEN権限のスコープとセルフホストランナーのセキュリティ設定が実運用の要


OllamaとGitHub Actionsを連携させる方法|セルフホストランナーにローカルLLMを導入してPRレビューと自動要約を実装する手順

「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜGitHub ActionsにローカルLLMが有効か

GitHub Actionsの標準ランナー(ubuntu-latest)はGitHubが管理するクラウドVM上で動く。コードレビューをClaude APIやGPT-4 APIで実装する場合、PRのたびにソースコードがそのAPIサービスの外部サーバーに送信される。

金融・医療・官公庁向けのシステム開発では、ソースコードを外部クラウドに送ること自体を禁じるセキュリティポリシーを持つ組織は珍しくない。知人のインフラエンジニアが担当したSIプロジェクトでも、コードレビュー自動化のためのAI APIを使いたかったが、コード外送禁止ポリシーに阻まれてあきらめたという話を聞いた。

セルフホストランナーを使えば、ジョブは組織管理のサーバー上で走る。OllamaをそのLinuxサーバーに同居させることで、差分コードはローカルネットワーク内から一切出ない。さらにAPIコストも発生しない。

一方、セルフホストランナーはGitHubが提供するランナーと比べてメンテナンス負荷が増える。パッチ適用・ランナーバイナリのアップデート・サーバー死活監視が必要になる。公開リポジトリで使う場合はフォーク元からのコード実行リスクもある。本記事で扱う構成はプライベートリポジトリ限定で運用することを前提にしている。

社内のコードを外部に出せない理由の整理については社内でChatGPTが使えないときの代替手段も参考にしてほしい。

セルフホストランナーをLinuxサーバーにインストールする

#### ステップ1. GitHub上でランナーを登録する GitHubのリポジトリページで「Settings」→「Actions」→「Runners」→「New self-hosted runner」を開く。OSは「Linux」・アーキテクチャは「x64」を選ぶ。表示されるコマンド群をコピーしておく(ダウンロードURLにはトークンが埋め込まれている)。 #### ステップ2. ランナー専用ユーザーを作成する セキュリティのため、rootではなく専用ユーザーでランナーを動かす。

# ランナー専用ユーザーの作成 $ sudo useradd -m -s /bin/bash ghrunner $ sudo su - ghrunner # ランナーの作業ディレクトリ作成 $ mkdir actions-runner && cd actions-runner

#### ステップ3. ランナーバイナリをダウンロードして設定する GitHubの画面に表示されたコマンドを実行する。URLとトークンは各リポジトリ・各組織で異なるため、必ず画面のコマンドをコピーして使うこと。

# ダウンロード(URLとトークンはGitHub画面のものに差し替える) $ curl -o actions-runner-linux-x64-2.321.0.tar.gz -L \ https://github.com/actions/runner/releases/download/v2.321.0/actions-runner-linux-x64-2.321.0.tar.gz $ tar xzf ./actions-runner-linux-x64-2.321.0.tar.gz # リポジトリへの接続設定(--url と --token はGitHub画面のものを使う) $ ./config.sh --url https://github.com/your-org/your-repo \ --token AXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \ --name "llm-runner-01" --labels "self-hosted,llm" --unattended # 接続確認 $ ./run.sh & Connected to GitHub Actions: Running in user mode. Listening for Jobs

#### ステップ4. systemdサービスとして常時起動させる `ghrunner` ユーザーから一旦 `exit` してrootに戻り、サービスを登録する。

$ exit # ghrunnerユーザーから抜ける # ランナー付属のインストールスクリプトを実行 $ sudo /home/ghrunner/actions-runner/svc.sh install ghrunner $ sudo /home/ghrunner/actions-runner/svc.sh start $ sudo /home/ghrunner/actions-runner/svc.sh status * actions.runner.your-org.llm-runner-01.service - GitHub Actions Runner Active: active (running)

Ollamaをセルフホストランナーサーバーにセットアップする

セルフホストランナーと同じサーバーでOllamaが動いていれば、ジョブからは `http://127.0.0.1:11434` で直接叩ける。

ランナーのジョブはデフォルトで `ghrunner` ユーザーで実行される。Ollamaが `ollama` ユーザーで動いている場合でも、ポート11434にHTTPで接続するだけなので権限の問題は起きない。

#### コードレビュー用のモデルをプル済みにする ジョブが走るたびにモデルをpullするとタイムアウトのリスクがある。サーバーの初期設定段階でモデルを手動pullしておく。

# コードレビュー用モデルをあらかじめ取得しておく $ ollama pull gemma3:9b-instruct-q4_0 # 確認 $ ollama list NAME ID SIZE MODIFIED gemma3:9b-instruct-q4_0 xxxxxxxxxxxx 5.5 GB 2 minutes ago # ローカルAPIが応答するか確認 $ curl -s http://127.0.0.1:11434/api/tags | python3 -m json.tool | head -10 { "models": [ { "name": "gemma3:9b-instruct-q4_0",

コードレビュー用途にはコンテキスト長と推論精度のバランスが重要だ。9Bクラスのモデルで十分なレビューコメントを生成できるが、70Bモデルを使うとより詳細な指摘が得られる。モデルの使い分けについてはローカルLLMのモデルを比較する方法も参照してほしい。

PR差分を取得してOllama APIに送るワークフローYAMLを作成する

リポジトリの `.github/workflows/` ディレクトリに `llm-code-review.yml` を作成する。

# .github/workflows/llm-code-review.yml name: Local LLM Code Review on: pull_request: types: [opened, synchronize] jobs: llm-review: runs-on: [self-hosted, llm] # セルフホストランナーのラベル指定 permissions: pull-requests: write # PRコメント投稿に必要 steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 # 全コミット履歴を取得(diff取得に必要) - name: Get PR diff id: diff run: | git diff origin/${{ github.base_ref }}...HEAD \ -- '*.py' '*.sh' '*.go' '*.js' '*.ts' \ > /tmp/pr_diff.txt echo "lines=$(wc -l < /tmp/pr_diff.txt)" >> $GITHUB_OUTPUT - name: Run LLM review id: review run: | DIFF=$(cat /tmp/pr_diff.txt | head -300) PROMPT="以下のコード差分をレビューしてください。\ バグ・セキュリティリスク・改善点を日本語で箇条書きで指摘してください。\ 問題がなければ「問題なし」とだけ返してください。\n\n${DIFF}" RESULT=$(curl -s http://127.0.0.1:11434/api/generate \ -d "{\"model\":\"gemma3:9b-instruct-q4_0\", \"prompt\":$(echo "$PROMPT" | python3 -c \ 'import sys,json; print(json.dumps(sys.stdin.read()))'), \"stream\":false}" \ | python3 -c "import sys,json; print(json.load(sys.stdin)['response'])") echo "result<> $GITHUB_OUTPUT echo "$RESULT" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Post review comment uses: actions/github-script@v7 with: script: | const review = `${{ steps.review.outputs.result }}`; await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: `## ローカルLLMコードレビュー\n\n${review}\n\n---\n*Powered by Ollama (gemma3:9b-instruct-q4_0) on self-hosted runner*` });

`head -300` でdiffを300行に制限しているのはOllamaのコンテキスト長(デフォルト2048トークン)を超えないためだ。大きなPRでは差分全体を一度に処理できない。次のセクションでプロンプトとモデルの設定を調整する方法を示す。

モデルとシステムプロンプトの選択・最適化方法

コードレビューの品質は使うモデルとプロンプトに大きく依存する。実運用で確認した傾向をまとめる。

**モデルの選び方**
9Bクラス(gemma3:9b-instruct-q4_0)はGPU非搭載サーバーのCPU推論でも1分以内にレスポンスが得られる。70Bクラスは精度が上がるが、VRAM 40GB以上を要しCPU推論では5~10分かかることもある。CI/CDのタイムアウト(デフォルト6時間だが組織設定で短縮される場合がある)と相談して選ぶこと。

**コンテキスト長の拡張**
大きなPRをまとめてレビューさせたい場合は `num_ctx` パラメータを増やす。ただしVRAM消費が増えるため注意が必要だ。

# コンテキスト長を4096に拡張してOllama APIを呼ぶ RESULT=$(curl -s http://127.0.0.1:11434/api/generate \ -d "{\"model\":\"gemma3:9b-instruct-q4_0\", \"prompt\":$(echo "$PROMPT" | python3 -c \ 'import sys,json; print(json.dumps(sys.stdin.read()))'), \"options\":{\"num_ctx\":4096}, \"stream\":false}" \ | python3 -c "import sys,json; print(json.load(sys.stdin)['response'])")

**Modelfileで専用モデルを作る**
コードレビュー専用のシステムプロンプトをModelfileに埋め込んでおくと、ワークフロー側のプロンプトをシンプルに保てる。

# Modelfile の例(code-reviewer という名前で登録) FROM gemma3:9b-instruct-q4_0 SYSTEM """ あなたは経験豊富なシニアエンジニアです。 コードの差分を受け取り、以下の観点で日本語・箇条書きでレビューしてください: 1. バグ・ロジックエラー 2. セキュリティリスク(SQLインジェクション・コマンドインジェクション・認証不備など) 3. パフォーマンス上の懸念 4. 可読性・命名・コメントの改善点 問題がなければ「問題なし」とだけ返してください。余分な挨拶・まとめ文は不要です。 """ PARAMETER num_ctx 4096 PARAMETER temperature 0.1 # モデルを登録 $ ollama create code-reviewer -f ./Modelfile $ ollama list | grep code-reviewer code-reviewer xxxxxxxxxx 5.5 GB just now

temperature を低め(0.1)に設定しているのはレビューコメントのばらつきを減らすためだ。コードレビューは正確性・一貫性が求められる用途なので、創造性より再現性を優先する。

PRへのコメント自動投稿とファイル分割レビューを実装する

大規模PRでは差分を300行に切り捨てるだけでは不十分な場合がある。変更ファイルごとに分割してレビューし、1ファイル1コメントにまとめる実装に切り替えることができる。

#!/bin/bash # review_by_file.sh # 変更ファイルを1つずつOllamaにレビューさせてJSONに積む CHANGED_FILES=$(git diff --name-only origin/$BASE_BRANCH...HEAD \ -- '*.py' '*.sh' '*.go' '*.js' '*.ts') REVIEW_RESULTS="" while IFS= read -r file; do [ -z "$file" ] && continue DIFF=$(git diff origin/$BASE_BRANCH...HEAD -- "$file" | head -200) [ -z "$DIFF" ] && continue RESULT=$(curl -s http://127.0.0.1:11434/api/generate \ -d "{\"model\":\"code-reviewer\", \"prompt\":$(printf 'ファイル: %s\n\n%s' "$file" "$DIFF" | \ python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))'), \"stream\":false}" \ | python3 -c "import sys,json; print(json.load(sys.stdin)['response'])") REVIEW_RESULTS+="### \`${file}\`\n${RESULT}\n\n" done <<< "$CHANGED_FILES" echo "$REVIEW_RESULTS" > /tmp/review_results.txt echo "done"

このスクリプトをワークフローの `Run LLM review` ステップに組み込み、 `/tmp/review_results.txt` の内容をPRコメントに投稿する。ファイル数が多い場合はジョブの実行時間が長くなるため、レビュー対象を `*.py` のみ、あるいは特定ディレクトリ以下に絞り込む工夫が実運用では必要になる。

`GITHUB_TOKEN` の権限スコープには注意が必要だ。ワークフローファイルの `permissions:` セクションで `pull-requests: write` を明示しないと、GitHub Actionsのデフォルト設定によってはコメント投稿が `403 Forbidden` で失敗する。Organization設定で「Default permissions」が「Read repository contents and packages」になっている場合は特に確認すること。

コミットメッセージ自動生成ワークフローを追加する

同じセルフホストランナーを使って、コミットメッセージを自動生成するワークフローも実装できる。開発者がコミットメッセージを書き忘れたPRに対して、差分から自動でメッセージ案を提案する。

# .github/workflows/llm-commit-suggest.yml(抜粋) - name: Suggest commit message id: suggest run: | DIFF=$(git diff origin/${{ github.base_ref }}...HEAD | head -150) PROMPT="以下のコード差分から、Conventional Commits形式の\ コミットメッセージを1行で提案してください(日本語可)。\ 形式: type(scope): description\n\n${DIFF}" MSG=$(curl -s http://127.0.0.1:11434/api/generate \ -d "{\"model\":\"code-reviewer\", \"prompt\":$(echo "$PROMPT" | python3 -c \ 'import sys,json; print(json.dumps(sys.stdin.read()))'), \"stream\":false}" \ | python3 -c "import sys,json; print(json.load(sys.stdin)['response'])") echo "message=$MSG" >> $GITHUB_OUTPUT - name: Post commit message suggestion uses: actions/github-script@v7 with: script: | const msg = `${{ steps.suggest.outputs.message }}`; await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: `## コミットメッセージ案\n\`\`\`\n${msg}\n\`\`\`\n*LLMによる自動生成です。内容を確認してから使用してください。*` });

2つのワークフロー(コードレビューとコミットメッセージ提案)はイベントトリガーを分けることもできるが、同じ `pull_request` トリガーで1つのワークフローにまとめる方がジョブの並走によるランナー競合を避けられる。

よくあるトラブルと対処法

**ランナーがGitHubに接続できない(offline表示になる)**
ランナーサービスが停止しているか、GitHub側でトークンが失効している可能性がある。

# ランナーサービスの状態確認 $ sudo systemctl status actions.runner.your-org.llm-runner-01.service # 停止していたら起動 $ sudo systemctl start actions.runner.your-org.llm-runner-01.service # ログ確認(接続エラーの原因調査) $ sudo journalctl -u actions.runner.your-org.llm-runner-01.service -n 50

トークンが失効している場合は、GitHubの「Settings→Actions→Runners」からランナーを削除して再登録する。ランナーバイナリ自体は残して `./config.sh` だけ再実行すれば再登録できる。

**Ollamaへのcurlが `Connection refused` になる**
ワークフローのジョブは `ghrunner` ユーザーで実行されるが、Ollamaが `OLLAMA_HOST=127.0.0.1:11434` で起動していれば同一サーバー上の全ユーザーから接続できるはずだ。Ollamaが起動しているか確認する。

# Ollamaの稼働確認(ghrunnerユーザーで実行) $ sudo -u ghrunner curl -s http://127.0.0.1:11434/api/tags {"models":[...]} # Ollamaが止まっていたら起動 $ sudo systemctl start ollama

**PRコメント投稿が403エラーになる**
ワークフローYAMLの `permissions:` セクションに `pull-requests: write` が設定されているか確認する。Organization管理者が「Actions permissions」→「Workflow permissions」を「Read repository contents」に限定している場合、ワークフロー側で上書き許可が必要だ。

**OllamaのAPIがタイムアウトする**
`curl` のデフォルトタイムアウトは無制限だが、ジョブのステップにはGitHub Actionsのタイムアウト設定がある。モデルのロード時間を含めて推論に時間がかかる場合は、ステップに `timeout-minutes: 10` を追加して明示的に設定しておく。

まとめ

OllamaとGitHub Actionsセルフホストランナーを連携させてPRレビューを自動化する手順をまとめた。
ステップ コマンド・設定 目的
①セルフホストランナー登録 ./config.sh --url ... --token ... GitHub Actionsとの接続確立
②systemdサービス化 svc.sh install ghrunner サーバー再起動後も自動起動
③モデル事前取得 ollama pull gemma3:9b-instruct-q4_0 ジョブ実行時のpullタイムアウト防止
④Modelfileで専用モデル作成 ollama create code-reviewer -f Modelfile システムプロンプトの固定化
⑤ワークフローYAML作成 .github/workflows/llm-code-review.yml PR差分→Ollama→コメント投稿の自動化
ローカルLLMをCI/CDパイプラインに組み込む最大の利点は、ソースコードをクラウド外に出さずにAIレビューを実現できる点だ。セルフホストランナーのメンテナンスコストは増えるが、APIコスト削減とセキュリティポリシーへの適合という2つのメリットは組織によっては大きな意義を持つ。

GitLabでの同様の構成については `ollama-gitlab-cicd-mr-code-review` の記事も参照してほしい。

OllamaのCI/CD組み込みと応用をハンズオンで体験する

セルフホストランナーへのOllama導入からワークフローYAMLの実装まで、手順を読むだけでなく実機で動かして習得したい方向けに、「ローカルAIマスターセミナー」を開催しています。
少人数(最大8名)ZOOMハンズオン形式で実施しています。

>> ローカルAIマスターセミナーの詳細を確認する
ローカルLLMの構築・運用に関する関連記事もあわせて参考にしてください。

・Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
・社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
・ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。