「毎回手でdocker buildしてdocker pushするのが面倒だし、push忘れが怖い」
Dockerイメージを手動でビルド・プッシュしていると、誰かが古いイメージのまま本番にデプロイしてしまうリスクがあります。GitにコードをプッシュしたタイミングでDockerイメージが自動的にビルドされてレジストリへ登録される仕組みを作れば、この問題は根本から解消されます。
この記事では、GitHub ActionsでDockerイメージを自動ビルドしてghcr.io(GitHub Container Registry)またはDocker Hubへプッシュする手順を解説します。タグ戦略(semver・sha・latest)、BuildKitのキャッシュ活用(cache-from/cache-to)、よくあるエラーと対処法まで、実際のworkflow.ymlサンプルとともに網羅します。動作確認環境はGitHub Actions ubuntu-24.04 runner、Dockerイメージのベースはubuntu:24.04です。
この記事のポイント
・docker/build-push-actionでビルド~プッシュをワンステップで自動化できる
・ghcr.ioはGITHUB_TOKENだけで認証できるため外部シークレット不要
・cache-type=ghaを使うとGitHub Actionsのキャッシュでビルド時間を大幅短縮できる
・タグはgit sha・semverタグ・latestを組み合わせて再現性と利便性を両立する
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜDockerイメージのCIビルドが必要なのか
手動でdocker build・docker pushを行っている現場で、よく起きるトラブルがあります。
・コードを変更したのにイメージをビルドし直すのを忘れて、古いイメージが本番で動き続ける
・開発者のPC環境によってビルド結果が微妙に異なり、「自分の環境では動くのに」問題が起きる
・ビルド手順がドキュメントに書かれておらず、担当者しかプッシュできない属人化が生まれる
GitHub ActionsでDockerイメージのビルドをCIに組み込むと、これらの問題がすべて解消されます。
・コードのプッシュ・マージをトリガーにイメージが自動でビルドされる
・CIランナー(クリーンな仮想環境)上でビルドするため環境差異が生まれない
・ワークフローがコードとして管理されるため、ビルド手順が属人化しない
・イメージにgit shaのタグが付くため、どのコミットから作られたイメージか追跡できる
GitHub ActionsのワークフローでDockerイメージをビルドする基本設定
1. docker/build-push-actionの基本構成
GitHub ActionsでDockerイメージをビルドするには、docker社が提供する公式アクション docker/build-push-action を使うのが現在のデファクトスタンダードです。Dockerのビルドエンジンとして docker/setup-buildx-action でBuildKitを有効化するのがセットになります。
# .github/workflows/docker-build.yml の基本形 name: Docker Build and Push on: push: branches: - main jobs: build: runs-on: ubuntu-24.04 steps: - name: リポジトリをチェックアウトする uses: actions/checkout@v4 - name: BuildKitを有効化する(必須) uses: docker/setup-buildx-action@v3 - name: Dockerイメージをビルドする(プッシュなし) uses: docker/build-push-action@v6 with: context: . push: false tags: my-app:latest
docker/setup-buildx-action はBuildKit(Docker 23.0以降のデフォルトビルドエンジン)をGitHub Actions上で確実に使えるようにするアクションです。これを入れないとキャッシュ機能が動作しないため、必ずセットで使ってください。
2. ghcr.ioへの認証とプッシュ
ghcr.io(GitHub Container Registry)は、GitHubリポジトリに付属するコンテナレジストリです。リポジトリに紐づいているため、外部のシークレットなしに GITHUB_TOKEN だけで認証できるのが最大のメリットです。
jobs: build: runs-on: ubuntu-24.04 # GITHUB_TOKENにパッケージの書き込み権限を付与する(必須) permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - name: ghcr.ioにログインする uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} # GITHUB_TOKENはActionsが自動生成するシークレット password: ${{ secrets.GITHUB_TOKEN }} - name: イメージをビルドしてghcr.ioへプッシュする uses: docker/build-push-action@v6 with: context: . push: true tags: ghcr.io/${{ github.repository }}:latest
permissions.packages: write を必ず設定してください。これがないと「denied: permission_denied」エラーになります。
3. Docker Hubへのプッシュ設定
Docker Hubへプッシュする場合は、DockerHubのユーザー名とアクセストークンをリポジトリのシークレットに登録してから使います。
- name: Docker Hubにログインする uses: docker/login-action@v3 with: # registry: を指定しない場合はDocker Hubがデフォルト username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: イメージをビルドしてDocker Hubへプッシュする uses: docker/build-push-action@v6 with: context: . push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/my-app:latest
Docker Hubの「Account Settings → Security → New Access Token」からアクセストークンを発行し、リポジトリの「Settings → Secrets and variables → Actions」に DOCKERHUB_TOKEN として登録します。パスワードの直接使用は非推奨です。
タグ戦略の設計(semver・sha・latest)
Dockerイメージのタグ設計は、CIを正しく運用する上で最も重要な決断の一つです。タグ設計を誤ると「どのコードがどのイメージか」が追跡できなくなり、障害時のロールバックが困難になります。
1. Gitタグ連動のsemverタグ
本番リリースには、v1.2.3 のようなGitタグに連動したsemverタグが適しています。docker/metadata-action を使うと、Gitタグから自動的に複数のイメージタグを生成できます。
- name: イメージメタデータ(タグ・ラベル)を生成する id: meta uses: docker/metadata-action@v5 with: images: ghcr.io/${{ github.repository }} tags: | # Gitタグが v1.2.3 のとき → 1.2.3, 1.2, 1, latest を自動生成 type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} # mainブランチへのプッシュ → edge タグを生成 type=edge,branch=main - uses: docker/build-push-action@v6 with: context: . push: true # metadata-actionが生成したタグを一括適用する tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }}
例えば git tag v1.2.3 && git push origin v1.2.3 を実行すると、1.2.3、1.2、1、latest の4つのタグが自動的にghcr.ioへプッシュされます。
2. コミットSHAタグで環境の再現性を確保する
本番環境では、latestのような浮動タグではなくコミットSHAに固定したタグを使うことを強く推奨します。git shaタグを使うと、デプロイしたイメージがどのコミットから作られたかを100%追跡できます。
- name: イメージメタデータを生成する(SHA付き) id: meta uses: docker/metadata-action@v5 with: images: ghcr.io/${{ github.repository }} tags: | # コミットSHA(先頭7文字): sha-a1b2c3d type=sha,prefix=sha-,format=short # mainブランチのみ latest タグも付ける type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
この設定だと、mainブランチへのプッシュごとに ghcr.io/org/repo:sha-a1b2c3d と ghcr.io/org/repo:latest の2つのタグが生成されます。KubernetesのDeploymentにはshaタグを指定し、latestは開発環境のdocker-composeでのみ使うという使い分けが実務では一般的です。
3. latestタグの扱いと注意点
latestタグは便利ですが、本番環境では注意が必要です。
・latestの意味: Dockerの仕様では「最新」を保証するわけではなく、単なる慣例タグ
・本番での問題: docker pull app:latest を再実行すると、意図せず新しいイメージに切り替わる
・推奨の使い分け: 本番はshaタグで固定、ステージングはedgeタグ、ローカル開発はlatest
BuildKitキャッシュでCIビルドを高速化する
GitHub ActionsでDockerをビルドすると、毎回クリーンな環境からビルドが始まるためキャッシュが効かず、ビルド時間が長くなりがちです。BuildKitのキャッシュ機能を使うと、前回のビルド結果を再利用して大幅に短縮できます。
1. gha(GitHub Actions Cache)型キャッシュの設定
最もシンプルで推奨されるキャッシュ方式が type=gha です。GitHub Actionsのキャッシュ機能(actions/cache相当)を自動的に使ってくれます。
- uses: docker/build-push-action@v6 with: context: . push: true tags: ghcr.io/${{ github.repository }}:latest # ghaキャッシュを使う設定(最もシンプル) cache-from: type=gha cache-to: type=gha,mode=max
mode=max を指定するとマルチステージビルドの中間イメージも含めてキャッシュされます。初回は効果がありませんが、2回目以降は変更のないレイヤーをキャッシュから復元するため、ビルド時間が大幅に短縮されます。
実際のログで確認すると、キャッシュが効いているときは以下のように表示されます。
# GitHub Actionsのログ出力例 # => [internal] load build definition from Dockerfile 0.1s # => [internal] load .dockerignore 0.1s # => [auth] library/ubuntu:pull token for registry-1.docker.io 0.0s # => CACHED [1/4] FROM docker.io/library/ubuntu:24.04 0.0s ← キャッシュヒット # => CACHED [2/4] RUN apt-get update && apt-get install -y ... 0.0s ← キャッシュヒット # => [3/4] COPY . /app 0.2s # => [4/4] RUN make build 8.3s # => exporting to image 1.1s
CACHED の行がキャッシュヒットしたレイヤーです。apt-getによるパッケージインストールなど時間のかかるステップがキャッシュに乗ると、数分かかっていたビルドが数十秒に短縮されます。
2. registry型キャッシュ(ghcr.io保存)の設定
キャッシュをレジストリ(ghcr.io)に保存する方式です。GitHub Actionsのキャッシュ容量制限(10GB)を気にしなくてよい反面、レジストリのストレージ容量を消費します。大規模なイメージや複数のブランチが並走する場合に有効です。
- uses: docker/build-push-action@v6 with: context: . push: true tags: ghcr.io/${{ github.repository }}:latest # キャッシュをghcr.ioに保存する cache-from: type=registry,ref=ghcr.io/${{ github.repository }}:cache cache-to: type=registry,ref=ghcr.io/${{ github.repository }}:cache,mode=max
3. マルチステージビルドとキャッシュの組み合わせ
マルチステージビルドとキャッシュを組み合わせると、本番イメージを軽量化しながらCIのビルド時間も短縮できます。
# Dockerfile のマルチステージビルド例 # ビルドステージ: 開発ツールを含む重いイメージ FROM golang:1.23 AS builder WORKDIR /app COPY go.mod go.sum ./ # 依存関係のダウンロードを別レイヤーにする(キャッシュが効きやすい) RUN go mod download COPY . . RUN go build -o /app/server ./cmd/server # 実行ステージ: ビルド成果物だけを含む軽量イメージ FROM ubuntu:24.04 AS runner RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* COPY --from=builder /app/server /usr/local/bin/server CMD ["/usr/local/bin/server"]
Goの依存関係ダウンロード(go mod download)を別レイヤーに分離するのがポイントです。go.mod・go.sumが変更されない限りこのレイヤーはキャッシュから復元されるため、ソースコードの変更だけではダウンロードのステップがスキップされます。
実践的なワークフローサンプル(semver+sha+ghaキャッシュ)
ここまでの設定を組み合わせた、実務で使えるワークフローのサンプルを示します。mainブランチへのマージとGitタグのpushの両方に対応した構成です。
name: Docker Build and Push on: push: branches: - main tags: - 'v*.*.*' pull_request: branches: - main jobs: build: runs-on: ubuntu-24.04 permissions: contents: read packages: write steps: - name: リポジトリをチェックアウトする uses: actions/checkout@v4 - name: BuildKitを有効化する uses: docker/setup-buildx-action@v3 - name: ghcr.ioにログインする(PRでは不要なためスキップ) if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: イメージタグとラベルを生成する id: meta uses: docker/metadata-action@v5 with: images: ghcr.io/${{ github.repository }} tags: | # v1.2.3 タグ付きpush → semverタグを生成 type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} # mainブランチpush → sha+latestタグを生成 type=sha,prefix=sha-,format=short type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} # PRビルド → pr-42 形式のタグを生成(レジストリへはpushしない) type=ref,event=pr - name: ビルドしてプッシュする uses: docker/build-push-action@v6 with: context: . # PRの場合はビルドのみ実施(プッシュしない) push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max
このワークフローの動作をまとめます。
・Pull Request時: ビルドのみ実行(ビルドが通るか確認)、レジストリへはプッシュしない
・mainブランチへのマージ時: sha-a1b2c3d と latest の2タグでプッシュ
・v1.2.3 タグのpush時: 1.2.3, 1.2, sha-a1b2c3d の3タグでプッシュ
トラブルシュート(認証エラー・キャッシュが効かない・タイムアウト)
「denied: permission_denied」が出た場合
ghcr.ioへのプッシュ時に permission_denied が出るケースは、ほぼ permissions の設定漏れです。ジョブレベルに以下を必ず追加してください。
# ジョブに permissions を追加する jobs: build: runs-on: ubuntu-24.04 permissions: contents: read packages: write # ← これが必須
また、Organization(組織)配下のリポジトリでghcr.ioを使う場合は、Organization側の「Packages」設定でパッケージの公開範囲を確認してください。Organization全体でパッケージ作成を無効にしていると、GITHUB_TOKENでの認証が通りません。
キャッシュが効かない場合
ghaキャッシュが効いているかどうかは、ビルドログで CACHED の表示を確認します。キャッシュが効かない主な原因は以下の3つです。
・Dockerfileの命令順序が悪い: 変更頻度が高いファイル(ソースコード)を先にCOPYすると、以降のすべてのレイヤーキャッシュが無効化される。変更の少ない依存関係ファイル(package.json、go.mod等)を先にCOPYして、その後にソースコードをCOPYするように順序を直す
・mode=max を指定していない: cache-to: type=gha のみだとデフォルトのmode=minが使われ、最終ステージのキャッシュしか保存されない。マルチステージビルドではmode=maxを指定する
・初回実行: キャッシュは最初の成功ビルドで生成されるため、初回は必ずキャッシュミスになる
docker/setup-buildx-action を入れ忘れた場合
docker/setup-buildx-action がないと、cache-from/cache-to を指定しても無視されます。ビルドログに WARNING: current buildx builder does not support cache export と出た場合はこれが原因です。
# checkout の直後に必ず追加する - uses: docker/setup-buildx-action@v3
ポート確認でコンテナの動作を検証したい場合
ビルドしたイメージをCIパイプライン内でテスト起動して、ポートが正しくListenされているかを確認したいケースがあります。GitHub Actionsのランナー上でコンテナを起動してから、ssコマンドで確認する方法を示します。
- name: テスト起動してポートを確認する run: | docker run -d --name test-app -p 8080:8080 ghcr.io/${{ github.repository }}:latest sleep 3 # ssコマンドでポートがListenされているか確認する ss -tlnp | grep 8080 # アプリへのリクエストが通るかも確認する curl -f http://localhost:8080/healthz docker rm -f test-app
ローカルPC上でもこの確認をするには ssコマンドや lsof コマンドが役立ちます。詳しい使い方はLinux ポート確認の全コマンド(ss・netstat・lsof)を参照してください。
本記事のまとめ
GitHub ActionsでDockerイメージを自動ビルドしてレジストリへプッシュする手順を解説しました。要点を以下にまとめます。
| やりたいこと | 設定・アクション |
|---|---|
| BuildKitを有効化する | docker/setup-buildx-action@v3(checkout直後に必ず入れる) |
| ghcr.ioへの認証をする | docker/login-action@v3 + permissions.packages: write |
| ビルドしてプッシュする | docker/build-push-action@v6 with: push: true |
| semver・sha・latestタグを自動生成する | docker/metadata-action@v5 + type=semver, type=sha を組み合わせる |
| ghaキャッシュでビルドを高速化する | cache-from: type=gha と cache-to: type=gha,mode=max を指定する |
| PRはビルドのみ、mainはプッシュと使い分ける | push: ${{ github.event_name != 'pull_request' }} で制御する |
| 認証エラー「permission_denied」を解消する | ジョブに permissions: packages: write を追加する |
DockerとCIを実務で使いこなすには、Linux基盤の理解が土台になります
GitHub ActionsでDockerのCI/CDを組むには、コンテナがLinuxカーネルのnamespace・cgroupを使っていることへの理解が運用の壁を下げます。ログが出ない、ポートが開かない、権限エラーになるといったトラブルを自力で解決できるかどうかは、Linux基盤の知識量に直結します。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。
「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 前のページへ:docker saveとdocker loadでイメージをオフライン移送する方法|閉域網サーバーへの持ち込み運用
- この記事の属するカテゴリ:Dockerへ戻る

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