RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

GitHub Actions で使うコンテナイメージタグの一元管理

この記事は ラクス Advent Calendar 2025 の20日目の記事です。

今回は、日々の開発や運用の中で「これ意外と便利だったな」と感じた小さな改善を紹介します。

背景・課題:CI 内でタグの定義場所が散らばる

GitHub Actions のジョブでコンテナイメージを指定する場合、たとえば以下のように container.image を書くことが多いと思います。

jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/ORG/REPO:v1.0.0

しかし、複数ジョブが存在するWorkflowだと、同じタグをあちこちで書くことになります。 更新時にすべて直すのが面倒で、修正漏れが発生しやすいのが悩みどころです。 また漏れが発生すると、テストやビルドが意図しないバージョンで動いてしまい、検知できるはずの不具合が見逃されるリスクもあります。

解決策:タグ指定にGitHub Actions の出力値を使う

こうしたタグ管理の悩みを解決するために、さまざまな手段を探していました。 そんな中、別件の改善をするにあたってWorkflowドキュメントを読んでいると、container.image にも変数が使えそうだ、ということがわかってきました*1

これまで漠然と container.image はベタ書きするしかないと思い込んでいたので、この発見は大きな転機でした。 実際に動作するか試してみたところ、問題なく動作したため、これを活用してタグ管理を一元化することにしました。

実際の構成:タグ生成ジョブ → 他ジョブが参照する

jobs:
  resolve_tag:
    runs-on: ubuntu-slim
    outputs:
      tag: ${{ steps.resolve.outputs.tag }}
    steps:
      - name: resolve tag
        id: resolve
        run: |
          TAG="v1.0.0"
          echo "tag=$TAG" >> "$GITHUB_OUTPUT"

  test:
    needs: resolve_tag
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/ORG/REPO:${{ needs.resolve_tag.outputs.tag }}

これで test ジョブの image が resolve_tag ジョブの出力を使うようになります。
ジョブを追加するときも、needs: resolve_tag から参照するだけです。

共通化:reusable workflow 化でタグ生成を完全に中央集約

Workflowファイル数が多い場合、resolve_tag ジョブを各ファイルにコピペするのも面倒です。 そこで、タグ生成部分を reusable workflow 化して共通化しました。

共通化という観点ではcomposite action にする方法もありますが、composite actionはジョブ内のステップとして展開されるような仕組みです。 container.imageはJOBレベルでの指定なので、reusable workflow を採用しました。 ※ステップ内に展開されるcomposite actionでも対応はできますが、outputsなどJOBレベルの構造を利用側に毎回書く必要があり、共通化のメリットが薄い

タグ生成用 workflow(共通化)

# .github/workflows/resolve_tag.yml
name: Resolve Image Tag
on:
  workflow_call:
    outputs:
      tag:
        value: ${{ jobs.resolve_tag.outputs.tag }}

jobs:
  resolve_tag:
    runs-on: ubuntu-slim
    outputs:
      tag: ${{ steps.resolve.outputs.tag }}
    steps:
      - name: resolve tag
        id: resolve
        run: |
          TAG="v1.0.0"
          echo "tag=$TAG" >> "$GITHUB_OUTPUT"

呼び出す側

jobs:
  resolve_tag:
    uses: ./.github/workflows/resolve_tag.yml

  test:
    needs: resolve_tag
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/ORG/REPO:${{ needs.resolve_tag.outputs.tag }}

こうすることで、タグは完全に一元管理でき、変更が楽になります。

(補足)docker compose にも応用可能

もしローカル開発用に docker compose でも同じタグを使いたい場合はタグの管理ファイルを作成すると、CIとの共通化が可能です。 例えば.envファイルであれば、そこに記載すればdocker composeのコマンド引数を変更することなく反映できます。

.env

IMAGE_TAG=v1.0.0

docker-compose.yml

services:
  app:
    build:
      context: .
      args:
        - IMAGE_TAG=${IMAGE_TAG}

Dockerfile

ARG IMAGE_TAG
FROM ghcr.io/ORG/REPO:${IMAGE_TAG}

...

.github/workflows/resolve_tag.yml

jobs:
  resolve_tag:
    runs-on: ubuntu-slim
    outputs:
      tag: ${{ steps.resolve.outputs.tag }}
    steps:
      - uses: actions/checkout@v6
      - name: resolve tag
        id: resolve
        shell: bash
        run: |
          source .env # .env から読み込み
          echo "tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"

.envファイルを使わない・使えない場合

.envファイルを使わない・使えない場合、docker composeのコマンド引数を変更しない、というのは難しいようでした。 私たちのケースではdocker composeをラップするようなMakefileを用意し、その中で環境変数を設定する形で対応しました。

# Makefileの機能(include, export)で環境変数を設定
include xxx.env
export IMAGE_TAG

docker-build:
    docker compose build --build-arg IMAGE_TAG=${IMAGE_TAG}

まとめ

  • GitHub Actions の needs.<job>.outputs.*container.image に使える
  • タグ生成ジョブを 1 箇所に置くだけで CI 全体のタグ管理が楽になる
  • ローカル(docker compose)に応用することも可能

小さな工夫ですが、導入すると CI の見通しがかなり良くなりました。
同じ悩みを持つ方の参考になれば幸いです。

Copyright © RAKUS Co., Ltd. All rights reserved.