「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権限のスコープとセルフホストランナーのセキュリティ設定が実運用の要
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜ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
# ダウンロード(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
$ 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",
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*` });
モデルとシステムプロンプトの選択・最適化方法
コードレビューの品質は使うモデルとプロンプトに大きく依存する。実運用で確認した傾向をまとめる。**モデルの選び方**
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 の例(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
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"
`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による自動生成です。内容を確認してから使用してください。*` });
よくあるトラブルと対処法
**ランナーが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
**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
ワークフロー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→コメント投稿の自動化 |
GitLabでの同様の構成については `ollama-gitlab-cicd-mr-code-review` の記事も参照してほしい。
OllamaのCI/CD組み込みと応用をハンズオンで体験する
セルフホストランナーへのOllama導入からワークフローYAMLの実装まで、手順を読むだけでなく実機で動かして習得したい方向けに、「ローカルAIマスターセミナー」を開催しています。
少人数(最大8名)ZOOMハンズオン形式で実施しています。
・Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
・社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
・ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:OllamaのsystemdでCPUとメモリ使用量を制限する方法|cgroup v2のリソースクォータで複数チームが使う共有Linuxサーバーを安定稼働させる手順
- この記事の属するカテゴリ:ローカルLLMへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます