はじめに

手動でDockerイメージをビルドしてレジストリにプッシュする作業を、毎回繰り返していませんか。本番環境への反映が遅れたり、タグ付けを間違えたりといったヒューマンエラーは、開発チームの生産性を大きく損なう原因となります。

CI/CDパイプラインにDockerを組み込むことで、コードのプッシュからイメージのビルド、テスト、レジストリへのプッシュまでを完全に自動化できます。本記事では、GitHub ActionsとDockerを連携させ、自動化されたビルド・デプロイパイプラインを構築する方法を体系的に解説します。

この記事を読み終えると、以下のことができるようになります。

  • GitHub ActionsでDockerイメージを自動ビルドできる
  • Docker Hubへのプッシュを自動化できる
  • ARM/AMD64のマルチプラットフォームビルドを実行できる
  • 効果的なイメージタグ戦略を設計・実装できる

前提として、GitHubリポジトリとDocker Hubアカウントを所持していること、Dockerfileの基本を理解していることを想定しています。

CI/CDパイプラインにおけるDockerの役割

まず、CI/CDパイプラインでDockerがどのような役割を果たすのかを理解しておきましょう。

従来のデプロイ課題とDockerによる解決

従来のデプロイでは、以下のような課題がありました。

課題 説明
環境差異 開発・ステージング・本番で動作が異なる
依存関係の管理 各環境でのライブラリバージョン管理が煩雑
デプロイの再現性 同じ手順でも結果が異なることがある
ロールバックの困難さ 以前の状態に戻すのに時間がかかる

Dockerコンテナを使うことで、これらの課題を解決できます。アプリケーションとその依存関係をイメージとしてパッケージ化することで、どの環境でも同じ動作を保証できます。

CI/CDにおけるDockerワークフロー

CI/CDパイプラインでのDockerの典型的なワークフローは以下のとおりです。

flowchart LR
    Push["コード\nプッシュ"] --> Build["ビルド\nイメージ\n作成"]
    Build --> Test["テスト\n実行"]
    Test --> Tag["タグ付け\n署名"]
    Tag --> Registry["レジストリ\nプッシュ"]
    Registry --> Deploy["本番\nデプロイ"]

このワークフローをGitHub Actionsで自動化することで、コードをプッシュするだけで本番環境へのデプロイまでを一気通貫で実行できます。

GitHub ActionsでのDockerイメージビルド

GitHub Actionsは、GitHubが提供するCI/CDプラットフォームです。Dockerイメージのビルドに必要な公式アクションが豊富に用意されており、簡単にパイプラインを構築できます。

Docker公式GitHub Actions

Dockerは以下の公式アクションを提供しています。

アクション 用途
docker/login-action レジストリへのログイン
docker/setup-buildx-action BuildKitビルダーのセットアップ
docker/setup-qemu-action マルチプラットフォームビルド用QEMUのセットアップ
docker/build-push-action イメージのビルドとプッシュ
docker/metadata-action タグとラベルの自動生成

基本的なビルドワークフロー

最もシンプルなDockerイメージビルドのワークフローを見てみましょう。リポジトリのルートに.github/workflows/docker-build.ymlを作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
name: Docker Build

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

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

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: false
          tags: myapp:latest

このワークフローのポイントは以下のとおりです。

  • onでトリガー条件を定義(mainブランチへのプッシュとPR)
  • actions/checkout@v4でリポジトリをチェックアウト
  • docker/setup-buildx-action@v3でBuildKitを有効化
  • docker/build-push-action@v6でイメージをビルド(push: falseでプッシュせずビルドのみ)

ビルドキャッシュの活用

CI/CDでのビルド時間を短縮するには、キャッシュの活用が重要です。GitHub Actionsのキャッシュ機能と連携させることで、レイヤーキャッシュを再利用できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
name: Docker Build with Cache

on:
  push:
    branches: ['main']

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

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: false
          tags: myapp:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

cache-fromcache-totype=ghaを指定することで、GitHub Actionsのキャッシュストレージを利用できます。mode=maxを指定すると、すべてのレイヤーをキャッシュします。

Docker Hubへの自動プッシュ設定

ビルドしたイメージをDocker Hubへ自動的にプッシュする設定を行います。

Docker Hubアクセストークンの作成

まず、Docker Hubでアクセストークンを作成します。

  1. Docker Hubにログインし、Account Settings > Security に移動
  2. New Access Token をクリック
  3. トークン名を入力し、Read & Writeの権限を選択
  4. 生成されたトークンをコピー

GitHubシークレットの設定

次に、GitHubリポジトリにシークレットを登録します。

  1. リポジトリのSettings > Secrets and variables > Actions に移動
  2. New repository secret をクリック
  3. 以下の2つのシークレットを登録
シークレット名
DOCKERHUB_USERNAME Docker Hubのユーザー名
DOCKERHUB_TOKEN 作成したアクセストークン

Docker Hubへのプッシュワークフロー

シークレットを使用してDocker Hubにログインし、イメージをプッシュするワークフローです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
name: Build and Push to Docker Hub

on:
  push:
    branches: ['main']
    tags: ['v*']

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ secrets.DOCKERHUB_USERNAME }}/myapp

      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

このワークフローのポイントは以下のとおりです。

  • docker/login-action@v3でDocker Hubにログイン
  • docker/metadata-action@v5でGitのリファレンスから自動的にタグを生成
  • push: trueでビルド後に自動プッシュ

GitHub Container Registryへのプッシュ

Docker Hubの代わりに、GitHub Container Registry(ghcr.io)を使用することもできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
name: Build and Push to GHCR

on:
  push:
    branches: ['main']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Container registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

GitHub Container Registryを使用する場合、GITHUB_TOKENを自動的に使用できるため、追加のシークレット設定が不要です。

マルチプラットフォームビルド(ARM/AMD64)

Apple Silicon MacやAWSのGravitonインスタンスなど、ARMアーキテクチャの普及に伴い、マルチプラットフォーム対応のDockerイメージの需要が高まっています。

マルチプラットフォームビルドの仕組み

Docker Buildxを使用すると、1回のビルドで複数のアーキテクチャ向けのイメージを生成できます。

flowchart TD
    Dockerfile["Dockerfile"]
    Dockerfile --> Buildx["Buildx +\nQEMU"]
    Buildx --> amd64["linux/amd64"]
    Buildx --> arm64["linux/arm64"]
    Buildx --> armv7["linux/arm/v7"]
    amd64 --> Manifest["Manifest List\n(マルチアーキテクチャ)"]
    arm64 --> Manifest
    armv7 --> Manifest

QEMUを使用してエミュレーションすることで、AMD64ランナー上でもARM向けイメージをビルドできます。

QEMUを使用したマルチプラットフォームビルド

以下のワークフローで、AMD64とARM64の両方に対応したイメージをビルドできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
name: Multi-Platform Build

on:
  push:
    branches: ['main']
    tags: ['v*']

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

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ secrets.DOCKERHUB_USERNAME }}/myapp

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

重要なポイントは以下のとおりです。

  • docker/setup-qemu-action@v3でQEMUをセットアップ(ARMエミュレーションに必要)
  • platforms: linux/amd64,linux/arm64で対象プラットフォームを指定

高速化のためのマトリクスビルド

QEMUによるエミュレーションは時間がかかります。ビルド時間を短縮するには、プラットフォームごとに並列ビルドを実行し、最後にマニフェストをマージする方法が有効です。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
name: Multi-Platform Build (Matrix)

on:
  push:
    tags: ['v*']

env:
  REGISTRY_IMAGE: username/myapp

jobs:
  build:
    strategy:
      fail-fast: false
      matrix:
        include:
          - platform: linux/amd64
            runner: ubuntu-latest
          - platform: linux/arm64
            runner: ubuntu-24.04-arm
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Prepare
        run: |
          platform=${{ matrix.platform }}
          echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV

      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY_IMAGE }}

      - name: Build and push by digest
        id: build
        uses: docker/build-push-action@v6
        with:
          context: .
          platforms: ${{ matrix.platform }}
          labels: ${{ steps.meta.outputs.labels }}
          tags: ${{ env.REGISTRY_IMAGE }}
          outputs: type=image,push-by-digest=true,name-canonical=true,push=true

      - name: Export digest
        run: |
          mkdir -p ${{ runner.temp }}/digests
          digest="${{ steps.build.outputs.digest }}"
          touch "${{ runner.temp }}/digests/${digest#sha256:}"

      - name: Upload digest
        uses: actions/upload-artifact@v4
        with:
          name: digests-${{ env.PLATFORM_PAIR }}
          path: ${{ runner.temp }}/digests/*
          if-no-files-found: error
          retention-days: 1

  merge:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Download digests
        uses: actions/download-artifact@v4
        with:
          path: ${{ runner.temp }}/digests
          pattern: digests-*
          merge-multiple: true

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY_IMAGE }}
          tags: |
            type=ref,event=tag
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}

      - name: Create manifest list and push
        working-directory: ${{ runner.temp }}/digests
        run: |
          docker buildx imagetools create \
            $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
            $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)

      - name: Inspect image
        run: |
          docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}

この方法では、ARM64向けビルドをARMネイティブランナー(ubuntu-24.04-arm)で実行するため、エミュレーションのオーバーヘッドがなく高速にビルドできます。

イメージタグ戦略のベストプラクティス

イメージタグはバージョン管理やデプロイ戦略において重要な役割を果たします。適切なタグ戦略を設計することで、運用の安全性と効率が大幅に向上します。

タグ戦略の基本パターン

以下のタグパターンを組み合わせて使用することが推奨されます。

タグパターン 用途
セマンティックバージョン v1.2.3 特定バージョンの追跡
メジャー.マイナー v1.2 マイナーバージョン内の最新追跡
メジャーのみ v1 メジャーバージョン内の最新追跡
latest latest 最新の安定版
SHA sha-a1b2c3d 特定コミットの追跡
ブランチ名 main, develop 開発・ステージング環境向け

metadata-actionによる自動タグ生成

docker/metadata-actionを使用すると、Gitのリファレンスから自動的に適切なタグを生成できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
- name: Extract metadata
  id: meta
  uses: docker/metadata-action@v5
  with:
    images: username/myapp
    tags: |
      # デフォルトブランチでのプッシュ時
      type=raw,value=latest,enable={{is_default_branch}}
      # ブランチ名
      type=ref,event=branch
      # PRの場合
      type=ref,event=pr
      # Git tag
      type=ref,event=tag
      # セマンティックバージョニング(v1.2.3 -> 1.2.3, 1.2, 1)
      type=semver,pattern={{version}}
      type=semver,pattern={{major}}.{{minor}}
      type=semver,pattern={{major}}
      # SHAの短縮形
      type=sha,prefix=sha-

この設定により、以下のようにタグが自動生成されます。

イベント 生成されるタグ
mainブランチへのプッシュ main, latest, sha-a1b2c3d
featureブランチへのプッシュ feature-xxx, sha-a1b2c3d
v1.2.3タグの作成 v1.2.3, 1.2.3, 1.2, 1, sha-a1b2c3d
PRの作成 pr-123, sha-a1b2c3d

latestタグの扱いに関する注意点

latestタグは便利ですが、本番環境での使用には注意が必要です。

[注意] latestタグの問題点

・どのバージョンか特定できない
・ロールバックが困難
・キャッシュの影響で古いイメージが使われる可能性
・再現性の確保が難しい

本番環境では、必ず特定のバージョンタグ(v1.2.3など)を使用することを推奨します。latestタグは開発環境やクイックテスト用途に限定しましょう。

イミュータブルタグの活用

セキュリティと再現性を高めるために、イミュータブル(不変)タグの活用を検討しましょう。

1
2
3
4
5
tags: |
  # SHAベースのイミュータブルタグ
  type=sha,format=long,prefix=sha-
  # タイムスタンプ
  type=raw,value={{date 'YYYYMMDD-HHmmss'}}

SHAやタイムスタンプベースのタグは一意であるため、同じタグで異なるイメージがプッシュされるリスクを排除できます。

実践的なCI/CDワークフロー例

ここまでの内容を組み合わせた、本番運用で使える完全なワークフロー例を紹介します。

リリース時の自動プッシュワークフロー

GitHubでリリースを作成した際に、自動的にマルチプラットフォームイメージをビルドしてプッシュするワークフローです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
name: Release

on:
  release:
    types: [published]

env:
  REGISTRY_IMAGE: username/myapp

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            ${{ env.REGISTRY_IMAGE }}
            ghcr.io/${{ github.repository }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
            type=raw,value=latest

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

このワークフローでは、Docker HubとGitHub Container Registryの両方に同時プッシュを行い、冗長性を確保しています。

PRでのビルドテストワークフロー

プルリクエスト時にビルドテストを実行し、プッシュはしないワークフローです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
name: PR Build Test

on:
  pull_request:
    branches: ['main']

jobs:
  build-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build image (no push)
        uses: docker/build-push-action@v6
        with:
          context: .
          push: false
          load: true
          tags: myapp:test
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Run container tests
        run: |
          docker run --rm myapp:test /app/test.sh

load: trueを指定することで、ビルドしたイメージをローカルのDockerデーモンにロードし、後続のテストで使用できます。

まとめ

本記事では、CI/CDパイプラインでのDocker活用について、GitHub Actionsを中心に解説しました。

学習した内容を整理すると、以下のとおりです。

  • GitHub ActionsでのDockerビルド: 公式アクションを使用して効率的にイメージをビルドできます
  • Docker Hubへの自動プッシュ: シークレット設定とワークフロー定義で完全自動化が可能です
  • マルチプラットフォームビルド: QEMUとBuildxにより、ARM/AMD64両対応のイメージを作成できます
  • イメージタグ戦略: metadata-actionを活用して、セマンティックバージョニングに基づく自動タグ付けを実現できます

CI/CDパイプラインにDockerを組み込むことで、コードの変更からデプロイまでを自動化し、開発チームの生産性を大幅に向上させることができます。まずは基本的なビルドワークフローから始めて、徐々にマルチプラットフォームビルドやセキュリティ強化へと発展させていくことをお勧めします。

次のステップとして、Docker Scoutによる脆弱性スキャンやアーティファクト署名を組み込むことで、よりセキュアなCI/CDパイプラインを構築できます。

参考リンク