Getting Started (更新: 2026/6/7)

GitHub ActionsでCI/CD入門:lintからデプロイまで最小構成で通す

CI/CD構築の入門。GitHub Actionsでlint→test→build→deployを1本のYAMLで通す最小手順、トリガー・Secrets・ブランチ保護・失敗時の扱いを、僕の事故込みでやさしく解説。

GitHub ActionsでCI/CD入門:lintからデプロイまで最小構成で通す

金曜の夕方、「ちょっとした修正だから」とmainに直pushしました。テストは手元で通っていた。少なくとも、そう思っていた。

月曜の朝、本番が真っ白でした。原因は、僕がコミットし忘れた1ファイル。手元では存在するから動く、でもリポジトリには無い。誰のレビューも、自動チェックも通らずに本番へ届いてしまった一発でした。

このときに痛感したのが、CI/CDは「上級者の贅沢」じゃなくて、一人開発でも最初に置くべき安全装置だということです。今日は、その最小の足場をGitHub Actionsで組みます。難しい最適化は後回しでいい。まずは「壊れた変更が本番に行かない」状態を作りましょう。

この記事の要点

  • CI/CDは「変更を自動で検査して、通ったものだけ届ける」仕組み。CIが検査、CDが配送です。
  • 最小構成は1本のYAMLで足りる。lint → test → build → deploy を順に並べるだけ。
  • 大事なのは賢さより順番。落ちる変更を止めることが目的で、テストを高度にするのは別の話。
  • Secrets(APIキー等)はコードに書かず、リポジトリの金庫に預ける。forkからのPRには渡さない。
  • workflowが緑でも、ブランチ保護で必須チェックに指定しないとmergeは止まらない。ここが最大の落とし穴。
  • matrix・cache・reusable workflowなどの応用はGitHub Actions高度化ガイドへ。この記事は土台だけに絞ります。

そもそもCI/CDって何をするものか

言葉が大げさなだけで、やっていることは単純です。

CI(継続的インテグレーション)は、変更を加えるたびに自動で検査する仕組みです。コードを書く→pushする→裏でlintとテストとビルドが走る→全部通れば緑、どれか落ちれば赤。これだけ。人間が「テスト回したっけ?」と思い出す必要が消えます。

CD(継続的デリバリー/デプロイ)は、検査を通った変更を環境へ届ける仕組みです。緑になったものをstaging(確認用の環境)へ自動で出し、本番へは人間がボタンを押してから出す。検査と配送を分けて考えるのがコツです。

僕は最初、この2つを一気に「全自動」にしようとして事故りました。順番は逆です。先にCI(検査)を固めて、CD(配送)は手動から始める。冒頭の本番真っ白事件も、CIさえあれば「コミット漏れでビルドが落ちる→赤になる→mergeできない」で止まっていました。

一番小さいCI:lintとtestを回す

理屈より動くものを見ましょう。GitHub Actionsは、リポジトリの .github/workflows/ 配下にYAMLを置くだけで動きます。まずはPull Requestのたびにlintとtestを走らせる、最小の1本です。

# .github/workflows/ci.yml
name: ci

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: npm

      - run: npm ci
      - run: npm run lint --if-present
      - run: npm test

このYAMLを置いてpushすれば、それだけで動きます。中身を上から読み解きます。

  • on:トリガー。「いつ走らせるか」です。ここでは「mainへのPull Request」と「mainへのpush」のとき。
  • permissions: contents: read は、このworkflowに渡す権限。最初は読み取りだけにしておくのが安全です。
  • runs-on: ubuntu-latest は、GitHubが用意してくれる使い捨ての作業マシン。
  • timeout-minutes: 15 は保険。無限ループや固まったテストで延々と課金されるのを防ぎます。
  • actions/checkout@v6 でリポジトリを取り込み、actions/setup-node@v6 でNode.jsを入れる。cache: npm は依存のダウンロードを次回から速くしてくれます。
  • npm ci で依存を入れ、linttest を順に実行。

--if-present は「そのscriptが無ければ黙って飛ばす」という意味です。lintをまだ設定していないプロジェクトでも、CI全体が落ちません。ただし注意してください。test には付けていないのは意図的です。テストscriptが無いのに「成功」と見なしてしまうと、検査していないのに緑になる、一番たちの悪い状態になります。

トリガーの選び方:pull_requestとpushの違い

入門でつまずきやすいのが「いつ走らせるか」です。ここを雑にすると、走ってほしいときに走らず、走らなくていいときに二重で走ります。

トリガー走るタイミング主な用途
pull_requestPRが開かれた・更新されたときmergeする前の品質チェック
push指定ブランチへpushされたときmain更新後の最終確認やデプロイ起動
workflow_dispatch画面の手動ボタンを押したとき本番デプロイ・rollbackなど人間が判断する処理
schedulecronで定期実行夜間の重いテストや依存の棚卸し

僕のおすすめは、PRチェックは pull_request、デプロイの起動は pushworkflow_dispatch、という分け方です。両方に同じ重い処理を書くと、PRを更新するたびにフルのデプロイ準備まで走って、お金も時間も溶けます。

ひとつ実体験を。on: push だけで全部やろうとした時期があって、PR段階では何も検査されず、mainにmergeして初めてテストが走る運用になっていました。これだと「mergeしてから赤くなる」ので手遅れです。検査はmerge前(pull_request)、配送はmerge後(push) と覚えておくと、まず外しません。

buildとdeployまでつなぐ最小パイプライン

CIが回るようになったら、builddeploy を後ろに足します。流れは「lint → test → build → deploy」。前の段が落ちたら次へ進ませない、という直列のゲートにするのがポイントです。

ここからはコピペでそのまま .github/workflows/ に置ける、デプロイまで含んだ1本を載せます。staging(確認環境)へは自動、本番へは手動ボタンを押したときだけ進む構成です。

# .github/workflows/deploy.yml
name: deploy

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: false

jobs:
  # 1. 検査 + ビルド。ここが落ちたらデプロイへ進ませない
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npm run lint --if-present
      - run: npm test
      - run: npm run build

  # 2. staging へは push されたら自動で出す
  deploy-staging:
    needs: build          # build が緑のときだけ動く
    runs-on: ubuntu-latest
    timeout-minutes: 10
    environment: staging  # staging 用の Secrets をここから読む
    env:
      APP_ENV: staging
      DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npm run deploy:staging

  # 3. 本番は「手動ボタンを押したとき」だけ進む
  deploy-production:
    if: github.event_name == 'workflow_dispatch'
    needs: deploy-staging
    runs-on: ubuntu-latest
    timeout-minutes: 10
    environment: production
    env:
      APP_ENV: production
      DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npm run deploy:production

3つのjobを直列につないでいます。needs: が「前のjobが緑のときだけ動く」という鎖です。build が落ちれば deploy-stagingdeploy-production も走りません。

本番の deploy-production には if: github.event_name == 'workflow_dispatch' を付けました。これで「mainへpushしただけでは本番に行かない、画面のRun workflowボタンを押したときだけ進む」になります。concurrency は、同じデプロイが二重起動してぶつかるのを防ぐ仕掛けです。cancel-in-progress: false にして、走行中のデプロイを途中で殺さないようにしています。

deploy:stagingdeploy:production の中身は、使っているホスティング(Vercel、Cloudflare、自前サーバーなど)で変わります。AWSへ短命トークンで安全に入れたい場合はAWSデプロイ実践が具体的です。

Secretsとブランチ保護:ここを外すと全部ザル

仕組みは組めました。でも、安全装置として効くかどうかはこの章で決まります。僕が一番事故った2点です。

Secretsはコードに書かない

APIキーやデプロイ用トークンは、絶対にYAMLやコードへ直書きしません。GitHubの Settings → Secrets and variables → Actions に登録して、${{ secrets.DEPLOY_TOKEN }} の形で呼び出します。

置き場所には階層があります。

  • Repository secrets: そのリポジトリのworkflow全体から読める。共通のものはここ。
  • Environment secrets: environment: production のように指定したjobだけが読める。本番トークンはここに隔離するのが鉄則です。

そして最重要のルール。forkからのPull RequestにはSecretsを渡さない。外部の人が送ってきた未信頼のコードにデプロイ用トークンが見えたら、抜き取られる経路になります。GitHubはデフォルトでforkのPRにsecretsを渡さない挙動ですが、自動レビューなどを動かすときは、自分のリポジトリ発のPRだけに絞る条件を付けます。Claude Code Actionを安全にPRレビューへ組み込む具体例はClaude Code CI/CD安全運用ガイドに詳しく書きました。

それと、echo $DEPLOY_TOKEN のようにログへ出す確認は絶対にやめてください。Actionsのログは見える人が多く、一度出力した秘密は漏れたものとして扱う必要があります。

ブランチ保護で「必須チェック」に指定する

これが入門で一番見落とされる点です。ここまで作ったworkflowが緑になっても、それだけではmergeを止める力はありません。CIが赤いままでもボタンを押せばmergeできてしまう。

止めるには、GitHubの Settings → Branches でmainに保護ルールを設定し、ci(workflow名)を Require status checks to pass before merging に登録します。これで初めて「CIが緑でないとmergeできない」状態になります。

僕は昔これを忘れていて、「CIあるから安心」と思い込んだまま、赤いPRをそのままmergeしていました。安全装置を付けたつもりで、スイッチを入れていなかったわけです。ブランチの切り方や保護の考え方はGitブランチ戦略の選び方、権限・鍵まわりの最低ラインは初心者が守る安全対策にまとめています。

失敗したときにどう動くか

CI/CDは「成功する道」だけ作っても半分です。落ちたときにどうするかを先に決めておかないと、本番障害の最中に初めて赤い画面を見て固まります。

落ちる場所は、だいたい4つに分かれます。

  1. lintで落ちた: コードの体裁や規約違反。たいてい一番浅い。該当行を直してpushし直すだけ。
  2. testで落ちた: ロジックの問題。手元で npm test を再現してから直す。CIのログだけ睨んでも進みません。
  3. buildで落ちた: 型エラー、依存の食い違い、設定ミス、環境差が定番。切り分けの順番はビルドエラー切り分けに体系立てました。
  4. deployで落ちた: トークン切れ、権限不足、デプロイ先の問題。ここはコードより環境設定を疑います。

そして本番に出た後の事故に備えて、戻す手段を先に用意します。最小なら、過去の正常なコミットを指定して再デプロイするだけのworkflowで十分です。

# 直前の正常コミットを確認して、その SHA で再デプロイを起動する例
git log --oneline -n 5

# 戻したいコミットの SHA を控えて、GitHub の画面か gh CLI で手動デプロイを起動
gh workflow run deploy.yml --ref main

ポイントは、rollback(巻き戻し)の判断と実行は人間の手元に残すこと。AIやworkflowに「自動で勝手に戻す」までやらせると、何が起きたか分からないまま状態が動いて、かえって復旧が遠のきます。

運用に乗せる前に、一度だけわざと失敗させる練習をしておくと強いです。lintを壊したPR、存在しないSecret、失敗するデプロイscriptを試して、「どのjobが赤くなるか」「通知は届くか」「誰が気づくか」を確認する。本番で初めて失敗画面を見るのと、練習で一度見ているのとでは、復旧速度がまるで違います。

よくある質問

Q. CIとCDは最初から両方入れるべき? いいえ。まずCI(lint・test・build)だけを必須にして、1〜2週間運用してください。既存の壊れを洗い出してから、stagingの自動デプロイ、最後に本番デプロイとrollbackの順で足すのが安全です。一気に全部入れると、どこで壊れたか分からなくなります。

Q. テストscriptがまだ無いプロジェクトはどうする? 最初の1本でいいので必ずテストを置いてください。テストが無い状態で --if-present を付けてCIを「緑」にすると、何も検査していないのに安心してしまう一番危ない状態になります。1本でもあれば「ビルドが通る」ことは保証できます。

Q. workflowは緑なのにバグが本番に出た。なぜ? CIは「テストで書いた範囲」しか守りません。テストが無い経路は素通りします。落ちた本番バグは、再現する最小のテストを足してから直すと、同じ事故が二度目から自動で止まります。テストの優先順位はテスト戦略の決め方が参考になります。

Q. matrixやcacheで速くしたい。どこを読めばいい? この記事の範囲を超えるので、複数バージョンを並列で回すmatrix、ビルドを速くするcache、重複を消すreusable workflowはGitHub Actions高度化ガイドにまとめています。土台ができてから足すのがおすすめです。

Q. デプロイ前にSecretsの値が正しいか確認したい。 値そのものをログに出さないでください。if: ${{ secrets.DEPLOY_TOKEN != '' }} のように「存在するか」だけを確認します。値の中身を echo で出すのは、漏えいと同じ扱いになります。

実際に試した結果

冒頭の本番真っ白事件のあと、僕が最初に入れたのは派手な全自動デプロイではなく、この記事の最小CI1本でした。lint → test → buildpull_request で回して、mainブランチの必須チェックに指定しただけ。それだけで、コミット漏れや壊れた変更がmerge段階で赤く止まるようになり、本番が真っ白になる類の事故は止まりました。

順番として効いたのは、CDを焦らなかったことです。検査(CI)を先に固めて、staging自動・本番手動から始める。本番の全自動化は、rollback手順を用意してから最後に足す。遠回りに見えて、これが一番つまずかない道でした。CI/CDは賢いより、止まるべきところで止まることが価値です。まずは1本、ci.yml を置いて、ブランチ保護のスイッチを入れるところから始めてみてください。

CLAUDE.mdにチームのルールを書いてClaude Codeにレビューさせる流れはCLAUDE.mdの原則に、運用テンプレートはClaudeCodeLabの教材にまとめています。公式の最新情報はGitHub Actions Workflow syntaxで確認してください。

#CI/CD #GitHub Actions #自動テスト #デプロイ #入門
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。