Docker 基礎 #5 レジストリ — Docker Hub, GHCR, push/pull

読了 7分

#3 まで作ったイメージは 自分のマシンの中だけ で生きていました。この記事ではそのイメージを 別のマシン — 同僚、CI サーバー、運用サーバー — でも使えるようにする仕組みを扱います。それが レジストリ (registry) です。

Docker 基礎 シリーズでこの記事の位置:

レジストリとは何か #

GitHub がコードのリモートリポジトリなら、レジストリはイメージのリモートリポジトリ です。例えのまま。

コードイメージ
作るgit commitdocker build
上げるgit pushdocker push
取るgit pulldocker pull
ホストGitHub、GitLabDocker Hub、GHCR、ECR

レジストリの種類は多いですが、最初に出会うのは普通二箇所です。

  • Docker Hub — Docker 公式レジストリ。python:3.14nginx:1.27 のような公式イメージがここにあります。無料アカウントでパブリックリポジトリを作れます。
  • GitHub Container Registry (GHCR) — GitHub が運営。GitHub リポジトリと権限を共有して、GitHub Actions と相性が良いので最近よく使われます。

これ以外にクラウドプロバイダーのレジストリ (AWS ECR、Google Artifact Registry、Azure Container Registry) がありますが、Docker から見ると全て同じ OCI 規約に従うので ログインコマンドだけ違って push/pull の流れは同じ です。

イメージ名の構造 #

Docker がイメージ名をどう解釈するかを一度解きほぐす価値があります。よく混乱するポイントだからです。

イメージ名の完全な形
[REGISTRY/]NAMESPACE/REPOSITORY[:TAG][@DIGEST]

例:

名前解きほぐすと
nginxdocker.io/library/nginx:latest
python:3.14-slimdocker.io/library/python:3.14-slim
myuser/myapp:1.0docker.io/myuser/myapp:1.0
ghcr.io/curtis/myapp:1.0GHCR の curtis/myapp 1.0
ghcr.io/curtis/myapp@sha256:abc...タグの代わりにダイジェストで固定

Docker はレジストリを省略すると docker.io (= Docker Hub) を仮定します。namespace を省略すると library (公式イメージの namespace)。タグを省略すると latest。だから nginx 一文字が実は docker.io/library/nginx:latest の短縮形です。

latest の落とし穴 #

latest は「最も最近」という意味ではありません。ただ タグを渡さなかったときのデフォルトの名前 に過ぎません。誰かが 1.27 をビルドして latest を一緒に付けないと、latest は古いイメージを指すこともあります。だから 運用では latest に依存せず明示的なバージョンタグ を使うのが定石です。

タグ戦略はこんな風によく組みます。

  • セマンティックバージョン: myapp:1.2.3
  • メジャー / マイナーエイリアス: myapp:1.2myapp:1
  • 環境: myapp:stagingmyapp:prod
  • Git コミット SHA: myapp:a1b2c3d
  • 常に同時に: myapp:latest

CI が一回のビルドで myapp:1.2.3myapp:1.2myapp:1myapp:latest を同時に刻んでおくと使う側が楽です。

docker tag — 別名を付ける #

イメージをビルドするとき -t で一度名前を付けましたが、同じイメージにさらに別名を付けることもできます。

既存のイメージに新しいタグ
docker tag hello-docker myuser/hello-docker:1.0
docker tag hello-docker myuser/hello-docker:latest

docker tag はデータコピーをせず ポインタだけ追加 します。だから即終わります。docker images で見ると同じ IMAGE ID に名前が複数付いているはずです。

レジストリにアップロードするには通常 名前にレジストリホスト / ユーザー名が入った形にタグを付け直します。

GHCR 用タグ
docker tag hello-docker ghcr.io/curtis/hello-docker:1.0

こうすることで Docker が「どこに送るべきか」を知ります。ただの hello-docker のような短い名前ではプッシュできません。

Docker Hub にプッシュ #

Docker Hub にアカウントを作って (hub.docker.com)、ターミナルでログインします。

Docker Hub ログイン
docker login
# Username: myuser
# Password: ******
# Login Succeeded

Personal Access Token を推奨: Docker Hub はパスワードの代わりに PAT (個人アクセストークン) の使用を推奨します。アカウント設定 → Security → New Access Token で作って、パスワード欄に PAT を入れてください。CI でも PAT が定石です。

ログイン情報は ~/.docker/config.json に保存されます。macOS/Windows の Docker Desktop は OS のキーチェーンに保存してより安全に保管します。

ではプッシュ:

タグ + プッシュ
docker tag hello-docker myuser/hello-docker:1.0
docker push myuser/hello-docker:1.0
# The push refers to repository [docker.io/myuser/hello-docker]
# 5e7d4abc...: Pushed
# 1.0: digest: sha256:abcdef... size: 1234

プッシュは レイヤー単位 で行われます。すでにレジストリにあるレイヤー (例: python:3.14-slim のベースレイヤー) はもう一度上がらず、変わったレイヤーだけ上がります。二度目のプッシュからとても速い理由がこれです。

別のマシンで取得:

別のマシンで pull
docker pull myuser/hello-docker:1.0
docker run --rm myuser/hello-docker:1.0

GitHub Container Registry (GHCR) にプッシュ #

GHCR は GitHub の権限モデルをそのまま使い、同じリポジトリの PAT で認証します。

1. PAT を作る #

GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) で新しいトークンを作って、権限に write:packages をチェックします。(private イメージ pull までするなら read:packages も。)

.env のようなファイルに保存
export CR_PAT=ghp_xxxxxxxxxxxxxxxxxx

2. ログイン #

GHCR ログイン
echo $CR_PAT | docker login ghcr.io -u curtis --password-stdin
# Login Succeeded

--password-stdin でトークンを stdin から読めばシェル履歴に残らず安全です。CI でも同じパターンを使います。

3. タグ + プッシュ #

ghcr タグ → プッシュ
docker tag hello-docker ghcr.io/curtis/hello-docker:1.0
docker push ghcr.io/curtis/hello-docker:1.0

プッシュ直後は GHCR のパッケージはデフォルトで private です。公開に変えるには GitHub のパッケージページで「Change package visibility」→ public。

GitHub Actions で #

GitHub Actions の中では PAT の代わりに自動発行される GITHUB_TOKEN をそのまま使います。

.github/workflows/build.yml (要約)
- uses: docker/login-action@v3
  with:
    registry: ghcr.io
    username: ${{ github.actor }}
    password: ${{ secrets.GITHUB_TOKEN }}

- uses: docker/build-push-action@v5
  with:
    push: true
    tags: ghcr.io/${{ github.repository_owner }}/myapp:${{ github.sha }}

手動で PAT を管理する必要がなく綺麗です。(このパターンは Docker 実践シリーズで深く扱います。)

ダイジェスト — 最も正確な固定 #

タグは人が読みやすいですが 不変 (immutable) ではありません。 誰かが myapp:1.2.3 を同じタグで再プッシュできます。だから正確な再現が大事な場面では ダイジェスト (SHA-256) を使います。

ダイジェストで取得
docker pull python@sha256:abc123def456...

プッシュのとき出力に digest: sha256:... が出ますが、それがそのイメージの永久識別子です。同じダイジェストは永遠に同じイメージ を指します。運用環境、セキュリティスキャン、コンプライアンスが大事な場面ではダイジェストで固定するのが定石です。

docker inspect でダイジェスト確認:

イメージのダイジェストを見る
docker inspect --format '{{index .RepoDigests 0}}' myuser/hello-docker:1.0
# myuser/hello-docker@sha256:abcdef...

docker pull — 取得 #

基本 pull
docker pull nginx:1.27
docker pull ghcr.io/curtis/hello-docker:1.0

よく使うオプション:

便利な形
docker pull --platform linux/amd64 myimage   # 特定アーキテクチャだけ
docker pull -a myuser/myapp                  # 全タグ

--platform は Apple Silicon マシンで amd64 イメージを強制で取るときによく使います。運用サーバが amd64 なのにローカル ARM でビルド / テストしようとする状況などで。(マルチアーキテクチャビルドは Docker 上級のテーマです。)

プライベートレジストリ — 一段落 #

会社の内部に自前のレジストリを置くことが多いです。Docker はイメージ自体をコンテナ化した registry イメージを提供します。

ローカルレジストリを立ち上げる
docker run -d --restart unless-stopped \
  -p 5000:5000 --name registry \
  -v registry-data:/var/lib/registry \
  registry:2

# プッシュ
docker tag myapp localhost:5000/myapp:1.0
docker push localhost:5000/myapp:1.0

研究室 / 社内ネットワークでセルフホストする流れですが、運用では普通 GHCR / ECR / Harbor のようなマネージド / フル機能レジストリを使います。とりあえずこういうことができるという程度で覚えておいてください。

よく出会う落とし穴 #

  • docker pushdenied: requested access to the resource is denied — ほぼ常にログイン / 権限 / タグ名のどれか。タグが myuser/... ではなく someoneelse/... になっているのがよくあります。
  • プッシュはできたのに別のマシンで pull できない — パッケージが private なのにそのマシンでログインしていない可能性。GHCR は private がデフォルトなのでよく出会います。
  • macOS でビルドしたイメージが運用サーバ (amd64) で立ち上がらない — Apple Silicon の arm64 イメージをプッシュしたケース。--platform linux/amd64 でビルドするか、Buildx でマルチアーキテクチャビルド。
  • latest が更新されていない感じがする — そのマシンの Docker がキャッシュされた latest を指しています。docker pull myimage:latest をもう一度叩けば OK。

まとめ #

この記事で掴んだ絵:

  • レジストリはイメージのリモートリポジトリ。GitHub ↔ Docker Hub / GHCR の例えそのまま
  • イメージ名 = [REGISTRY/]NAMESPACE/REPO[:TAG][@DIGEST]。省略時は docker.io/library/...:latest がデフォルト
  • docker tag は別名追加、docker push はレイヤー単位アップロード
  • Docker Hub は Personal Access Token、GHCR は GitHub PAT (または Actions の GITHUB_TOKEN) でログイン
  • 運用では latest 依存を避けてセマンティックバージョンタグ + Git SHA を一緒に刻む
  • 正確な固定が必要なら ダイジェスト (@sha256:…) で参照

次の記事 (#6 .dockerignore とビルドコンテキスト) ではイメージが膨れたりビルドが遅くなる最も多い原因 — ビルドコンテキスト の正体と、それを扱う .dockerignore の作成パターン、そして #2 で一度触れた レイヤーキャッシュ を本格的に組みます。

X