
この記事は ラクス Advent Calendar 2025 の20日目の記事です。
今回は、日々の開発や運用の中で「これ意外と便利だったな」と感じた小さな改善を紹介します。
- 背景・課題:CI 内でタグの定義場所が散らばる
- 解決策:タグ指定にGitHub Actions の出力値を使う
- 実際の構成:タグ生成ジョブ → 他ジョブが参照する
- 共通化:reusable workflow 化でタグ生成を完全に中央集約
- (補足)docker compose にも応用可能
- まとめ
背景・課題: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 の見通しがかなり良くなりました。
同じ悩みを持つ方の参考になれば幸いです。