🏷️ ‘latest’タグは今すぐ捨てるべき理由 — コンテナイメージタグの不変性(Immutability)を完全に理解する

「’latest’を使うと便利だけど…なぜ実務では使うなと言われるのだろう?」今日はその理由を徹底的に掘り下げていきます。

>


🎯 この記事で扱うこと

  • コンテナイメージタグがmutable(可変)であると何が起こるか
  • Immutableタグとは何か、なぜそれが標準とされるのか
  • 実務でよく使われるタグ戦略パターン(SemVer、Git SHA、日付ベース)
  • ECR・GCR・Harborでタグの不変性を強制設定する方法
  • CI/CDパイプラインで不変タグを自動化する例

📌 導入 / 背景

コンテナを初めて学ぶとき、誰もが docker pull nginx:latest の一行から始めます。手軽で、常に最新バージョンが手に入るように見え、直感的で分かりやすいです。

しかし、実際の運用環境でlatestタグをそのまま使用して失敗した事例は数えきれません。

昨日までは確かに動いていたのに、今日デプロイしたら突然アプリが落ちました。

この言葉の原因の多くは、まさにmutableタグにあります。同じタグ名なのにイメージの内容が変わってしまうこと、これが核心的な問題です。

コンテナエコシステムが成熟するにつれて、現在ではイメージタグをimmutable(不変)に管理することが事実上の業界標準となっています。Kubernetes、GitOps、DevSecOps、どの分野を深掘りしても、最終的にこの原則にたどり着きます。


🔍 MutableタグとImmutableタグ、正確には何が違うのか?

Mutableタグとは?

同じタグ名で異なるイメージを上書きできる状態です。

# 午前10時のビルド
docker build -t myapp:latest .
docker push myapp:latest
# → latest = コミットabc123イメージ

# 午後3時のビルド(コード変更後)
docker build -t myapp:latest .
docker push myapp:latest
# → latest = コミットdef456イメージ(午前のイメージは消滅)

latest以外にも、stable、production、v1のようなタグも同じ方法で上書きするとmutableになります。

Immutableタグとは?

一度pushしたタグは、決して別のイメージで上書きできないポリシーです。

# 特定のGit SHAまたはバージョンでタグ付け
docker build -t myapp:1.4.2 .
docker push myapp:1.4.2
# → 1.4.2はこのイメージに永久固定

# 後で同じタグでpushを試みた場合
docker push myapp:1.4.2
# ❌ ERROR: タグの不変性が有効です。既存のタグを上書きできません。

核心は、「タグ = 特定イメージの固有識別子」となる点です。


⚡ Mutableタグが引き起こす現実的な問題

1️⃣ 再現不可能なビルド/デプロイ環境

latestタグを使用すると、今日デプロイしたイメージと1週間後にロールバックを試みた際のイメージが異なる可能性があります。その間にdocker pull myapp:latestが上書きされていれば、ロールバック自体が無意味になります。

2️⃣ セキュリティ監査の困難さ

「このイメージにはどのようなライブラリが含まれていたか?」を追跡する必要がある場合、mutableタグは根拠を提供できません。CVEが発生した際に、「その時点でデプロイされたイメージが正確に何であったか」を証明することができません。

3️⃣ Kubernetesにおけるキャッシュ問題

Kubernetesは同じタグのイメージをノードでキャッシュします。imagePullPolicy: IfNotPresent 設定時、タグが同じであれば、新しいイメージに更新されたことに気づかず、既存のキャッシュを使い続けます。

# 危険な設定例
spec:
  containers:
  - name: myapp
    image: myapp:latest          # ← mutableタグ
    imagePullPolicy: IfNotPresent # ← キャッシュ優先、常にpullしない

4️⃣ 同時デプロイ時の競合状態(Race Condition)

マルチインスタンス環境でlatestを同時にpullすると、インスタンスごとに異なるイメージを実行する状況が発生する可能性があります。これは障害原因の特定が非常に困難になります。


🏗️ 実務でよく使われるImmutableタグ戦略

戦略1: Git SHAベース(最も強力な追跡性)

# CIパイプラインの例(GitHub Actions)
IMAGE_TAG=$(git rev-parse --short HEAD)  # 例: a3f5c12

docker build -t myapp:${IMAGE_TAG} .
docker push myapp:${IMAGE_TAG}
長所 短所
コミットとイメージの1:1マッピング 人間が読みにくい
完璧な追跡性 バージョンの意味を把握しにくい

### 戦略2: SemVer(セマンティックバージョン)ベース

# v1.4.2形式
docker build -t myapp:1.4.2 .
docker push myapp:1.4.2

MAJOR.MINOR.PATCH体系で変更規模を直感的に表現できるため、チームコミュニケーションに有利です。

戦略3: Git SHA + SemVer併用(ベストプラクティス)

VERSION="1.4.2"
GIT_SHA=$(git rev-parse --short HEAD)

# SemVerタグ: リリース用
docker push myapp:${VERSION}

# SHAタグ: 追跡用
docker push myapp:${GIT_SHA}

# latestは参照用のみ(開発/テスト環境)
docker push myapp:latest

こうすることで、「1.4.2が正確にどのコミットなのか」をいつでも逆追跡できます。

戦略4: 日付 + ビルド番号ベース

# 20260414-001形式
TAG=$(date +%Y%m%d)-${BUILD_NUMBER}
docker push myapp:${TAG}

リリースサイクルが日付中心のチームに適しています。


💻 レジストリ別タグ不変性強制設定

AWS ECR

aws ecr put-image-tag-mutability 
  --repository-name myapp 
  --image-tag-mutability IMMUTABLE

またはTerraformで:

resource "aws_ecr_repository" "myapp" {
  name = "myapp"

  image_tag_mutability = "IMMUTABLE"  # MUTABLEがデフォルトなので、必ず明示的に指定

  image_scanning_configuration {
    scan_on_push = true
  }
}

Harbor(自社構築レジストリ)

Harbor UI → プロジェクト設定 → 「Immutable Tags」ルールを追加:

# タグパターン: v* で始まるタグのみimmutableを適用
Tag Pattern: v*
Repository Pattern: **

Google Artifact Registry

gcloud artifacts repositories update myapp-repo 
  --location=us-central1 
  --update-immutable-tags=true  # タグ不変性ポリシーを有効化

🤖 GitHub Actions CI/CDでの不変タグ自動化

name: Build and Push

on:
  push:
    branches: [main]

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

      - name: Set image tag
        id: tag
        run: |
          # 短縮SHA + 日付の組み合わせ
          echo "IMAGE_TAG=$(date +%Y%m%d)-$(git rev-parse --short HEAD)" >> $GITHUB_ENV

      - name: Build image
        run: docker build -t ${{ secrets.ECR_REGISTRY }}/myapp:${{ env.IMAGE_TAG }} .

      - name: Push image
        run: docker push ${{ secrets.ECR_REGISTRY }}/myapp:${{ env.IMAGE_TAG }}

      - name: Update Kubernetes manifest
        run: |
          # GitOps方式: マニフェストのタグを新しいイメージタグに置き換え
          sed -i "s|image: myapp:.*|image: myapp:${{ env.IMAGE_TAG }}|g" k8s/deployment.yaml
          git commit -am "chore: update image tag to ${{ env.IMAGE_TAG }}"
          git push

こうすることで、デプロイごとに固有のタグが自動生成され、Kubernetesマニフェストに記録が残ります。後で「その時デプロイされたイメージが何だったか」をGit履歴で確認できます。


⚠️ 注意事項 / よくある間違い

🚫 latestを運用環境で使用しないこと 開発・テストで便宜上使うのは構いませんが、stagingとproductionでは絶対に禁止です。

🚫 Immutable設定後のタグ衝突処理の不備 同じタグを再pushするとレジストリがエラーを返します。CIパイプラインでタグ生成ロジックを必ずユニークに設計する必要があります。(同じコミットを2回ビルドすると? → SHAは同じ → エラー発生 → ビルド番号の組み合わせを推奨)

🚫 Digestをタグと混同するケース イメージダイジェスト(sha256:abc…)はイメージ内容のハッシュであり、元々不変です。タグが不変であるべきというのは、タグ名が常に同じダイジェストを指すべきであるという意味です。

🚫 レジストリポリシーなしにコンベンションだけで管理 「私たちはlatestを使わないと約束しました」だけでは不十分です。ECR Immutabilityのようなレジストリレベルの強制ポリシーがあって初めて、ミスを根本的に防ぐことができます。


✅ まとめ / 終わりに

区分 Mutableタグ Immutableタグ
再現性 ❌ 同一タグ ≠ 同一イメージ ✅ 同一タグ = 同一イメージ
セキュリティ監査 ❌ 履歴追跡不可 ✅ デプロイイメージ完全追跡
ロールバック ❌ イメージが消失する可能性あり ✅ 常に正確なバージョンに復帰
コラボレーション ❌ 混乱の可能性あり ✅ 明確なコミュニケーション
運用リスク 🔴 高い 🟢 低い

コンテナイメージタグの不変性は、単なる慣習ではなく、GitOps・DevSecOps・再現可能なビルドの基盤となる核心的な原則です。今すぐレジストリに不変性ポリシーを有効にし、CIパイプラインでGit SHAベースのタグ自動化を適用してみてください。

次のステップとして、Image Signing(Cosign)SBOM(Software Bill of Materials)によるイメージ整合性検証まで拡張することで、セキュリティレベルがさらに向上します。


Comments

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です