2026年5月25日

2026年5月25日

DockerでCI/CDパイプラインを構築する|ビルド・テスト・プッシュ・デプロイの設計と GitHub Actions の例

Docker を使った CI/CD パイプラインは、「コードの変更ごとに同じ手順でイメージを作り、テストに通ったイメージだけをレジストリに登録し、そのイメージをサーバーで入れ替える」流れとして組み立てます。ポイントは、テストしたイメージとデプロイするイメージを同一のものにすることと、どのイメージがどこで動いているかを後から追えるようにすることです。この記事では、パイプラインの各段階で何をするか、GitHub Actions での具体例、秘密情報の扱い、デプロイと切り戻しの設計までを説明します。

パイプラインの全体像

Docker を中心にした CI/CD は、おおむね次の段階で構成します。

  1. チェック:コードの静的解析や単体テストを実行する
  2. ビルド:Dockerfile からイメージを作る
  3. イメージのテスト:作ったイメージを起動して、動作や脆弱性を確認する
  4. プッシュ:テストに通ったイメージにタグを付けてレジストリへ登録する
  5. デプロイ:サーバーやクラスターで新しいイメージに入れ替える

「CI(継続的インテグレーション)」は1〜4、「CD(継続的デリバリー/デプロイ)」は5に当たります。最初からすべてを自動化する必要はなく、1〜4を自動化し、5は手動の承認を挟む形から始めると、運用に慣れながら段階的に広げられます。

イメージのタグ設計を先に決める

パイプラインを書く前に決めておきたいのが、イメージに付けるタグのルールです。ここが曖昧だと、障害時に「今どのコードが動いているのか」が分からなくなります。

  • コミット ID のタグ(例:sha-3f2a9c1):どのコードから作ったかが一意に分かります。デプロイにはこのタグを使います。
  • バージョンのタグ(例:1.4.0):リリース時に Git のタグから付けます。
  • **latest やブランチ名のタグ**:便利ですが、指す中身が変わるタグです。本番のデプロイ指定には使わない方が安全です。

レジストリによっては、同じタグの上書きを禁止する設定(タグの不変化)ができます。使える場合は有効にしておくと、意図しない上書きを防げます。さらに厳密にするなら、タグではなくダイジェスト(@sha256:...)でデプロイ対象を指定します。

GitHub Actions での実装例

GitHub Actions と GitHub Container Registry(ghcr.io)を使った例です。プルリクエストではビルドとテストまで、main ブランチへのマージではプッシュまでを行います。

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

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read
  packages: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: docker/setup-buildx-action@v3

      - name: Build image for test
        uses: docker/build-push-action@v6
        with:
          context: .
          load: true
          tags: my-app:test
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Run tests inside the image
        run: docker run --rm my-app:test ./vendor/bin/phpunit

      - name: Log in to GHCR
        if: github.event_name == 'push'
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Push image
        if: github.event_name == 'push'
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:sha-${{ github.sha }}
          cache-from: type=gha

この例で押さえておきたい点を説明します。

  • テストはイメージの中で実行する。 CI のランナー上で直接テストするのではなく、作ったイメージを起動してテストすることで、本番と同じ環境での動作を確認できます。テストに通ったイメージだけがプッシュされます。
  • 2回目のビルドはキャッシュから組み立てられる。 プッシュ用のビルドは、テスト用ビルドと同じキャッシュを使うため、ほとんどのレイヤーが再利用されます。厳密に「テストしたものと同じバイト列」をプッシュしたい場合は、テスト用イメージにタグを付け直して docker push する構成にします。
  • 権限は最小限にする。 permissions で、ワークフローの GITHUB_TOKEN が持つ権限を必要なものだけに絞っています。
  • アクションのバージョン。 例の @v4 や @v6 は執筆時点のメジャーバージョンです(2026年10月現在)。各アクションのリポジトリで最新のメジャーバージョンを確認してください。サプライチェーン攻撃への対策として、外部のアクションをコミット ID で固定する運用も推奨されています。

例のテストコマンド(PHPUnit)は、テスト用ツールを含むイメージであることを前提にしています。本番イメージに開発用の依存を入れたくない場合は、Dockerfile にテスト用のステージを用意し、target で切り替えてテストします。

タグにはコミット ID の全桁(github.sha)を使っています。ghcr.io のイメージ名は小文字である必要があるため、リポジトリ名やオーナー名に大文字が含まれる場合は小文字に変換してください。docker/metadata-action を使うと、ブランチ名・Git タグ・短いコミット ID などのタグをルールに沿って自動生成できます。

イメージの脆弱性スキャンを組み込む

テストの段階で、イメージに含まれる OS パッケージやライブラリの既知の脆弱性を調べておくと、問題のあるイメージを本番に出す前に気付けます。Trivy などのスキャナーは CI から実行でき、重大度の高い脆弱性が見つかったらジョブを失敗させる設定もできます。

ただし、ベースイメージ由来の脆弱性は修正版がまだ出ていないこともあります。すべてをブロック条件にすると開発が止まるため、最初は結果を記録するだけにして、修正可能で重大度が高いものから失敗条件に加えていくのが現実的です。

秘密情報をイメージに入れない

CI/CD で最も気を付けたいのが秘密情報の扱いです。

  • イメージに焼き込まない。 データベースのパスワードや API キーを ENV や COPY でイメージに入れると、イメージを取得できる人なら誰でも読み出せます。実行時に環境変数やシークレット管理の仕組みで渡します。
  • ビルド時に必要な秘密は BuildKit のシークレットで渡す。 非公開リポジトリから依存を取得するトークンなどは、RUN --mount=type=secret を使うとイメージのレイヤーに残りません。docker/build-push-action の secrets 入力から渡せます。
  • **ARG で秘密を渡さない。** ARG の値はイメージの履歴(docker history)から読める場合があります。
  • CI のシークレットに保存する。 GitHub Actions なら Secrets に登録し、ワークフローのログに出力しないようにします。プルリクエストのワークフローでは、フォークからの実行にシークレットを渡さない設定が基本です。

デプロイの段階を設計する

単一サーバーの場合

VPS 1台で Docker Compose を使って WordPress などを動かしている場合は、SSH でサーバーに接続し、新しいタグを指定して入れ替える方法が手軽です。

# サーバー側で実行する例(IMAGE_TAG は compose.yaml の image: で参照する変数)
export IMAGE_TAG=sha-3f2a9c1
docker compose pull app
docker compose up -d app
docker compose ps

CI から自動で実行する場合は、デプロイ専用の SSH 鍵を用意し、実行できるコマンドをサーバー側で制限しておきます。GitHub Actions の Environments を使うと、本番へのデプロイ前に承認者の承認を必須にできます。また、concurrency を設定して、同じ環境へのデプロイが同時に走らないようにします。

WordPress をイメージにする場合の注意

WordPress のテーマやプラグインのコードはイメージに含められますが、アップロードされたメディア(wp-content/uploads)とデータベースはイメージの外、ボリュームやマネージドサービスに置きます。イメージを入れ替えるたびにアップロードファイルが消える、という事故を防ぐためです。管理画面からのプラグイン更新はイメージと食い違う原因になるので、DISALLOW_FILE_MODS 定数で管理画面からの変更を止め、更新はパイプライン経由に統一する方法もあります。

デプロイ後の確認と切り戻し

デプロイしたら、ヘルスチェック用の URL にアクセスして応答を確認する処理をパイプラインに入れます。失敗したら、直前に動いていたタグを指定して同じ手順で戻します。コミット ID のタグを使っていれば、「1つ前に何が動いていたか」はデプロイ履歴からすぐに分かります。

注意が必要なのはデータベースのスキーマ変更です。イメージは戻せても、変更済みのデータベースは自動では戻りません。新旧どちらのコードでも動くようにスキーマ変更を段階的に行うか、デプロイ前にバックアップを取る手順を必須にしてください。

まとめ

  • パイプラインは「チェック→ビルド→イメージのテスト→プッシュ→デプロイ」の段階で考えます。
  • デプロイにはコミット ID などの変わらないタグを使い、latest に頼らないようにします。
  • テストはイメージの中で実行し、通ったイメージだけをレジストリに登録します。
  • 秘密情報はイメージに焼き込まず、BuildKit のシークレットや実行時の環境変数で渡します。
  • 本番デプロイには承認を挟み、切り戻しの手順とデータベースのバックアップを用意しておきます。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲