コンテナレジストリとイメージ管理

コンテナレジストリとは

コンテナレジストリは、コンテナイメージを保管して配布するサーバーです。手元で docker build したイメージは、そのマシンのディスクにしかありません。本番で動くのは別のマシンですから、いったんどこかに置いて向こうから取りに行ってもらう必要があります。その置き場がレジストリです。

docker push ghcr.io/acme/payment-api:1.4.2   # 手元 → レジストリ
docker pull ghcr.io/acme/payment-api:1.4.2   # レジストリ → 本番サーバー

[[Docker]] の Dockerfile が FROM node:22-slim で始まるとき、あの node:22-slim もレジストリから pull されています。イメージを「もらう側」ではすでにレジストリを使っていて、この記事はそれを「渡す側」から見た話です。

レジストリ・リポジトリ・タグ

イメージの長い名前は、3つの階層に分解できます。

ghcr.io / acme/payment-api : 1.4.2
   ↑            ↑              ↑
レジストリ    リポジトリ       タグ
(サーバー)  (アプリごとの棚)  (棚の中の1つを指す可変のラベル)

ホスト名を省略すると Docker Hub が既定になります。node:22-slimdocker.io/library/node:22-slim の省略形です。自社のレジストリへ push したいのにホスト名を書き忘れ、Docker Hub 宛てになって認証エラーで止まる、というのが最初のつまずきどころです。

push と pull はイメージ全体ではなくレイヤー単位で転送されます。相手側にすでにあるレイヤーは送られないため、2回目以降の push が数秒で終わることも珍しくありません。チームでベースイメージを揃えると転送量もディスク使用量も減ります。

latestを使わない理由

タグは可変のラベルです。同じ :latest に何度でも push でき、そのたびに以前の中身から剥がれて新しいイメージを指します。ここが事故の温床です。

  • 3台のうち1台だけ再起動がかかり、そのタイミングで pull し直した結果、その1台だけ別のコードが動いていた
  • 障害時に戻そうとしたが、「1つ前の latest」というタグは存在せず、戻す先が分からない

対策はイミュータブルタグです。一度付けたタグは二度と別のイメージに付け替えない、と決めます。1.4.2 のような [[セマンティックバージョニング]] の番号や、ビルド元の Git コミットのハッシュ(sha-9f3c1ab)がよく使われます。さらに厳密に固定したいときはダイジェストを使います。@sha256: に続く値はイメージの中身から計算されたハッシュなので、付け替えられません。

指定方法中身が変わる?主な用途
:latest変わる手元のお試し。本番では使わない
:1.4.2 / :sha-9f3c1ab運用で変えないと決める通常のデプロイ
@sha256:8b1c...絶対に変わらない完全な再現性が要るとき

公開・非公開とイメージの衛生

公開するかどうかはリポジトリ単位の設定です。Docker Hub も GitHub Container Registry も Amazon ECR も、公開・非公開の両方を作れます。

公開範囲pull の条件主な用途
パブリック誰でも pull できるOSSやベースイメージの配布
プライベート認証が必要自社アプリのイメージ

匿名 pull には回数制限があることが多く、CI が急に「too many requests」で落ちるのはこれが原因です。ログインして pull するか、自分のレジストリへコピーしておくと安定します。プライベートレジストリでは pull 側に資格情報が要り、[[Kubernetes]] なら Pod にイメージ取得用のシークレットを紐付けます。その鍵の配り方は [[シークレット管理]] の問題です。

イメージはアプリだけでなく、ベースイメージの OS パッケージも丸ごと抱えています。土台の弱点はそのまま自分のイメージの弱点です。多くのレジストリは push 時にスキャンして CVE を一覧します([[脆弱性管理とCVE]])。効くのは、小さいベースイメージ(-slim など)で同梱物を減らすことと、コードを変えなくても定期的に再ビルドして土台の修正を取り込むことです。コミットごとにタグを作ると数千件たまるので、「直近100件とリリースタグは残す」といった保持ポリシーも先に決めておきます。

CIがビルドし、本番がpullする

git push
  → CI: テスト → docker build -t <registry>/app:<コミットのハッシュ> → docker push
  → デプロイ: マニフェストのイメージ名を <コミットのハッシュ> に更新
  → 実行環境: そのタグを pull して起動

重要なのはビルドが1回だけであることです。検証環境で確認したイメージと本番で動くイメージが同一になり、環境ごとの差は環境変数で吸収します。「検証は通ったのに本番でだけ動かない」の多くは、環境ごとにビルドし直していることが原因です。[[CI/CD]] のパイプラインは、この push までを担当します。ロールバックは「1つ前のタグを指定して pull し直す」だけになり、[[デプロイ戦略]] のブルーグリーンやカナリアが成立するのも、戻したい版がレジストリにそのまま残っているからです。

初学者向けポイント

  • タグは「どのイメージか」を指すあだ名にすぎません。あだ名は付け替えられる、と覚えておくと latest の危うさが腑に落ちます
  • push できないときは、まず名前(ホスト名の有無)と docker login の2点を疑う。権限エラーの大半はこのどちらかです

関連技術とのつながり

  • [[Docker]] — レジストリに置くイメージを作る側。FROM の取得元もレジストリ
  • [[CI/CD]] — イメージをビルドして push するまでを自動化する場所
  • [[Kubernetes]] — マニフェストに書いたイメージをレジストリから pull して起動する
  • [[デプロイ戦略]] — 過去のイメージが残っているからこそ、素早い切り戻しが成立する
  • [[脆弱性管理とCVE]] — push されたイメージのスキャンで見つかる弱点への対処
Q: イメージ名 `ghcr.io/acme/payment-api:1.4.2` の `1.4.2` が指すものはどれ?
- [ ] レジストリのサーバー名
- [ ] リポジトリ(アプリごとの棚)の名前
- [x] リポジトリの中の1つのイメージを指すタグ
解説: 「レジストリ / リポジトリ : タグ」の3階層です。タグは棚の中の1つを指す可変のラベルです。

Q: 本番のデプロイで `:latest` タグを避けるべき理由として本文が挙げたのはどれ?
- [x] 同じタグに何度でもpushできるため、指す中身が変わり戻す先も分からなくなる
- [ ] latestタグはレジストリにpushできない仕様だから
- [ ] latestタグを使うとpullの転送量が増えるから
解説: タグは可変です。付け替えられると台ごとに動くコードがずれ、ロールバック先も特定できなくなります。

Q: イメージのダイジェスト(`@sha256:...`)による指定の特徴はどれ?
- [ ] pullするたびに最新のイメージへ自動更新される
- [x] 中身から計算されたハッシュなので、指すイメージが後から変わらない
- [ ] プライベートレジストリでは認証なしでpullできるようになる
解説: ダイジェストはイメージの中身のハッシュです。付け替えられないため、完全な再現性が必要な場面で使います。