冒頭まとめ
docker pull、docker run、docker compose up、docker build で次のエラーが出た場合、ログイン不足だけが原因とは限りません。
Error response from daemon: pull access denied for OWNER/IMAGE,
repository does not exist or may require 'docker login':
denied: requested access to the resource is denied
この文は、考えられる原因をそのまま2つ並べています。
repository does not exist
→ 指定したリポジトリが存在しない
may require 'docker login'
→ 非公開リポジトリで、認証またはpull権限が足りない
重要なのは、access denied と表示されても、リポジトリが存在するとは限らないことです。反対に、repository does not exist と表示されても、削除済みとは限りません。非公開リポジトリは、権限を持たない利用者からは存在を確認できない場合があります。
ただし、Registryの仕様が404と403を同じものとして定義しているわけではありません。CNCF DistributionのRegistry HTTP API V2仕様は、次のように区別しています。
| 状態 | HTTP | Registryのエラーコード |
|---|---|---|
| 認証が必要 | 401 | UNAUTHORIZED |
| 操作を許可されていない | 403 | DENIED |
| リポジトリ名が存在しない | 404 | NAME_UNKNOWN |
| リポジトリはあるがタグやdigestがない | 404 | MANIFEST_UNKNOWN |
混同が起きるのは、その手前に認証処理があるためです。Registryは最初に401と WWW-Authenticate を返し、クライアントは指定された認証サービスへ、対象リポジトリの pull 権限を含むトークンを要求します。権限のない主体には、要求した権限を含まないトークンが返ることがあります。その後のRegistry要求は拒否され、Docker CLIは不存在と権限不足の両方を含む案内へまとめます。
したがって、最初に docker login を繰り返すのではなく、Dockerがどの名前へアクセスしたかを確定します。
docker pull nginx
→ docker.io/library/nginx:latest
docker pull example/app
→ docker.io/example/app:latest
docker pull registry.example.com/team/app:1.2
→ registry.example.com/team/app:1.2
Docker公式のイメージ名の説明でも、nginx は docker.io/library/nginx:latest と同じ意味です。ホスト名、名前空間、リポジトリ名、タグのどれかが想定と違えば、正しいアカウントでログインしても直りません。
切り分けの中心は次の順序です。
1. 実際に解決された完全なイメージ名を確認する
2. そのホストと名前空間にリポジトリが存在するか確認する
3. 非公開なら、そのRegistryへログインする
4. ログインした主体にpull権限があるか確認する
5. リポジトリへ入れた後で、指定したタグが存在するか確認する
エラーの概要
Dockerでイメージを取得するとき、Docker CLIは直接ファイルを探すのではありません。選択中のDocker EngineまたはBuildKitが、イメージ名からRegistryを決め、認証を行い、manifestとlayerを順に取得します。
Docker CLI
↓ pull要求
Docker EngineまたはBuildKit
↓ manifest要求
Registry
↓ 401と認証先・必要scope
認証サービス
↓ 許可された範囲のBearer token
Registry
↓ manifestとlayer、または拒否
Registryのトークン認証仕様では、クライアントが求めた操作と、その主体へ実際に許可された操作の共通部分をトークンへ入れます。たとえば pull,push を求めても、pullだけ許可されていればpullだけが入り、何も許可されていなければ空になります。認証サービスは、この不足自体をトークン発行時のエラーにする必要はありません。
この仕組みでは、次の2つが利用者側で同じ拒否に見え得ます。
その名前のリポジトリが存在しない
→ pullを許可する対象がない
非公開リポジトリは存在するが、その主体に権限がない
→ pullを許可できない
Docker Hubでは、非公開リポジトリは検索結果に表示されず、権限を与えられた利用者だけがアクセスできます。そのため、権限を持たない状態で画面検索とpullの両方に失敗しても、不存在とは確定できません。
一方、公開リポジトリへ到達できていて、タグだけがない場合は、通常は次の系統になります。
manifest for OWNER/IMAGE:TAG not found: manifest unknown
pull access denied はリポジトリへ入る境界、manifest unknown はそのリポジトリ内のタグやdigestを探す境界です。ただし、非公開リポジトリでは先に認証で止まるため、存在しないタグを指定していても、権限を直すまでは manifest unknown に進めないことがあります。
まず最初に:完全なイメージ名を確定する
第一に、失敗ログに出た名前をそのまま読みます。
pull access denied for my-app
この場合、Docker Hub上の公式イメージ用名前空間へ補完されます。
docker.io/library/my-app:latest
自分のDocker Hubアカウント example にある my-app を取得したいなら、正しい指定は次です。
docker pull example/my-app:latest
第二に、ホスト名を確認します。
example/my-app
→ Docker Hub
ghcr.io/example/my-app
→ GitHub Container Registry
registry.example.com/example/my-app
→ 指定したRegistry
Docker Hubへログインしても、ghcr.io や自社Registryの権限は得られません。ログイン先は、イメージ名の先頭にあるRegistryと一致させます。
第三に、タグを確認します。タグを省略すると latest が使われます。
docker pull example/my-app
# example/my-app:latest を要求する
公開済みのタグが v1.4.2 だけなら、latest は自動生成されません。存在するタグを明示します。
docker pull example/my-app:v1.4.2
第四に、ComposeやDockerfileが実際に使う値を確認します。
docker compose config
docker build --progress=plain .
Composeの環境変数展開後に、image: が空、古い名前、別Registryになっていないかを見ます。ビルドでは、どの FROM または COPY --from の解決で止まったかを確認します。
よくある原因と解決手順
原因1:名前空間を省略し、Docker Hubのlibraryを見ている
自分のリポジトリを短い名前だけで指定すると、Docker Hub上の自分のアカウント名は補完されません。
Before(docker.io/library/my-app:latest を探す):
docker pull my-app
After(所有者の名前空間を含める):
docker pull example/my-app:latest
DockerfileとComposeでも同じです。
FROM example/my-app:latest
services:
app:
image: example/my-app:latest
docker login は名前を修正する処理ではありません。library/my-app が存在しないなら、Docker Hubへログインしても要求先は変わりません。
原因2:リポジトリ名、所有者名、Registryが違う
次の違いはすべて別の取得先です。
example/my-app
examples/my-app
example/myapp
registry.example.com/example/my-app
公開元のREADME、Composeファイル、CI変数、Registry画面で、完全な参照名を照合します。特に組織移管、リポジトリ削除、製品名変更の後は、古い参照が残ることがあります。
Docker Hubのリポジトリ名は作成後に変更できません。Docker Hubの作成資料にも、既存リポジトリはrenameできないと記載されています。名称を変えた運用では、通常は新しいリポジトリを作り、イメージを新しい参照へ公開します。古い名前が自動転送されるとは考えないでください。
原因3:非公開リポジトリへ未認証でアクセスしている
対象が非公開なら、まずそのRegistryへ認証します。
Docker Hubの場合は次のとおりです。
docker login
docker pull example/private-app:1.2
自社Registryの場合は、ホスト名と必要ならポートを指定します。
docker login registry.example.com
docker pull registry.example.com/team/private-app:1.2
docker login registry.example.com:5000
docker pull registry.example.com:5000/team/private-app:1.2
docker login の公式資料では、ログイン先にURLのpathを付けず、ホスト名と必要なポートだけを指定します。
# 誤り
docker login registry.example.com/team
# 正しい形
docker login registry.example.com
CIでは、秘密をコマンド引数へ直接書かず、標準入力から渡します。
printf '%s' "$REGISTRY_TOKEN" |
docker login registry.example.com \
--username "$REGISTRY_USER" \
--password-stdin
トークン本体、~/.docker/config.json、資格情報保存先の内容をログへ出さないでください。
原因4:ログインには成功したが、pull権限がない
Login Succeeded は、資格情報が認証サービスに受け入れられたことを示します。任意の非公開リポジトリをpullできるという意味ではありません。
Login Succeeded
denied: requested access to the resource is denied
この場合は、次を確認します。
ログインした利用者が想定したアカウントか
対象が個人の非公開リポジトリならcollaboratorか
組織リポジトリなら、対象teamまたはroleにpull権限があるか
組織用トークンなら、対象リポジトリが許可範囲に含まれるか
パスワードを何度作り直しても、対象リポジトリの許可は増えません。Registry管理者またはリポジトリ所有者側で、正しい主体へreadまたはpull権限を付けます。
原因5:タグを省略し、存在しないlatestを要求している
タグを省略したときに使われる latest は、最新時刻のイメージを自動で探す機能ではありません。latest という名前のタグです。
Before(存在しない latest を要求):
docker pull example/my-app
After(公開済みのタグを明示):
docker pull example/my-app:1.4.2
リポジトリへの参照権限がある状態なら、タグ不足は通常、次のように manifest unknown で判別できます。
manifest for example/my-app:latest not found: manifest unknown
非公開リポジトリで権限もタグも不足している場合は、権限の検査が先です。pull access denied を直した後に manifest unknown が現れることがあります。これは原因が変わったのではなく、次の検査段階まで進んだ結果です。
原因6:CIだけ別の認証設定を使っている
手元で docker login しても、その資格情報は自動でCIへ渡りません。Dockerは通常、実行した利用者の設定または資格情報保存先を使います。Linuxでは $HOME/.docker/config.json、Windowsでは %USERPROFILE%/.docker/config.json が標準の設定場所です。
CIでは、pullする処理と同じjob、同じ実行利用者、同じDocker設定でログインします。
export DOCKER_CONFIG="$RUNNER_TEMP/docker-config"
mkdir -p "$DOCKER_CONFIG"
printf '%s' "$REGISTRY_TOKEN" |
docker login registry.example.com \
--username "$REGISTRY_USER" \
--password-stdin
docker pull registry.example.com/team/app:1.2
sudo docker pull と通常の docker login を組み合わせると、資格情報を読む利用者が分かれる場合があります。権限回避のためだけに sudo を追加せず、ログインとpullを同じ実行環境へそろえます。
原因7:Dockerfileのstage名を間違え、外部イメージとしてpullしている
COPY --from の値は、以前のbuild stage、名前付きcontext、または外部イメージを指せます。Dockerfileの公式仕様にあるとおり、stage名として見つからなければ、イメージ参照として解決される構成があります。
Before(定義は builder、参照は build):
FROM golang:1.25 AS builder
WORKDIR /src
COPY . .
RUN go build -o /out/app ./cmd/app
FROM scratch
COPY --from=build /out/app /app
ログでは、存在しない build という外部イメージを取得しようとして、次のように見えることがあります。
pull access denied for build, repository does not exist or may require authorization
stage名を一致させます。
FROM scratch
COPY --from=builder /out/app /app
この場合、Registryへのログインは不要です。誤って外部イメージ扱いされた文字列を直します。
原因8:ローカルだけにあるイメージを、別のbuilderがpullしようとしている
Dockerfileに次の指定があるとします。
FROM my-local-base:latest
通常のイメージ保存先に my-local-base:latest があっても、選択中のBuildKit builderが別の保存領域を使っていれば、Registryへ取得しに行くことがあります。
docker image inspect my-local-base:latest
docker buildx ls
docker buildx inspect
ローカルのDocker Engineと同じ保存先を使う必要があるなら、docker driverのbuilderを選びます。
docker buildx use default
docker buildx inspect
docker buildx build .
docker driverの公式資料では、Docker Engine内蔵のBuildKitを使い、作成結果をローカルのimage storeへ自動で読み込むと説明されています。一方、docker-container、kubernetes、remote driverは別のBuildKitを使います。builderの実行場所が別なら、基礎イメージを共有Registryへpushし、完全な参照名で取得できるようにします。
--load は、buildの結果をローカルのimage storeへ読み込む指定です。別のbuilderへ既存の基礎イメージを渡す指定ではないため、入力側の pull access denied を直す目的では使いません。
原因9:Composeのimageとbuildの関係が想定と違う
Composeに image: だけがあれば、ローカルに見つからない場合はRegistryから取得します。
services:
app:
image: example/app:latest
ローカルのDockerfileから作る意図なら、build: を設定します。
services:
app:
build:
context: .
image: example/app:local
環境変数を使っている場合は、展開後の値を確認します。
services:
app:
image: ${REGISTRY}/${NAMESPACE}/app:${IMAGE_TAG}
docker compose config
空の変数、余分な /、誤ったRegistry、意図しない latest がないかを見ます。
補足:似ているが別のもの
manifest unknown
manifest unknown
manifest for OWNER/IMAGE:TAG not found
リポジトリへの参照には進めたものの、指定したタグまたはdigestに対応するmanifestがない状態です。リポジトリ名ではなく、タグ、digest、公開処理を確認します。
no matching manifest for linux/arm64
no matching manifest for linux/arm64/v8 in the manifest list entries
タグは存在しますが、現在のOS・CPUに対応するmanifestがありません。--platform、公開済みの対応環境、multi-platform buildを確認します。認証の問題ではありません。
too many requests
Docker Hubのpull回数制限は、pull access denied ではなく429と制限用の文言で返されます。Docker Hub公式のpull制限資料にも、上限到達時はmanifest要求へ429を返すと記載されています。ログインや契約によって上限条件は変わりますが、リポジトリ名の修正とは別の問題です。
x509、connection refused、timeout
x509: certificate signed by unknown authority
connect: connection refused
i/o timeout
これらは、RegistryへのTLS検証、接続、通信時間切れです。Registryから DENIED や NAME_UNKNOWN を受け取る前の失敗なので、権限追加ではなく証明書、ホスト名、port、proxy、firewallを確認します。
requested access to the resource is deniedがpushで出る
docker push では、pullではなくpush権限が必要です。正しいリポジトリへログインできていても、read-onlyの主体はpushできません。また、タグ付けした参照の名前空間が自分の所有先かを確認します。
docker image tag app:local example/app:1.0
docker push example/app:1.0
切り分けの順序
- エラーを出した処理がpull、run、Compose、Dockerfileのどれかを確認する。
- ログに出たイメージ名を、Registry、名前空間、リポジトリ、タグへ分ける。
- 省略されたRegistryが
docker.io、名前空間がlibrary、タグがlatestになっていないか確認する。 - 公開元の資料またはRegistry画面で、完全な参照名が正しいか確認する。
- 非公開なら、イメージ名と同じRegistryへログインする。
- ログインした主体が想定したアカウントか、対象へのpull権限があるか確認する。
- 権限を通過した後、タグまたはdigestが存在するか確認する。
- Dockerfileなら、
FROMとCOPY --fromのstage名を確認する。 - Composeなら、
docker compose configで環境変数展開後のimage:とbuild:を確認する。 - CIやBuildKitだけで失敗するなら、資格情報の保存先とbuilderの実行場所を確認する。
確認コマンド集
直接pullし、対象名と最終エラーを確認します。
docker pull OWNER/IMAGE:TAG
ローカルにあるイメージ名とdigestを確認します。
docker image ls --digests
docker image inspect OWNER/IMAGE:TAG
manifestを取得できるか確認します。
docker manifest inspect OWNER/IMAGE:TAG
Composeの展開後設定を確認します。
docker compose config
docker compose config --images
Dockerfileの取得位置を詳しく表示します。
docker build --progress=plain .
BuildKit builderを確認します。
docker buildx ls
docker buildx inspect
現在のDocker設定場所を確認します。
printf 'DOCKER_CONFIG=%s\n' "${DOCKER_CONFIG:-$HOME/.docker}"
自社Registryへ安全な形でログインします。
printf '%s' "$REGISTRY_TOKEN" |
docker login registry.example.com \
--username "$REGISTRY_USER" \
--password-stdin
資格情報やトークンの本文は表示しません。
Editor’s Note
pull access denied の文言が不存在と権限不足を同時に挙げる理由は、Mobyの実装履歴に残っています。
2018年8月の変更(Include original error when translating distribution errors)には、2つの比較例があります。
存在するbusyboxに、存在しないタグを指定
→ manifest for busybox:... not found
存在しないnosuchimageを指定
→ pull access denied for nosuchimage,
repository does not exist or may require 'docker login'
同じ変更のコードでは、Registryから DENIED を受け取ったときに複合案内を作り、MANIFEST_UNKNOWN なら manifest ... not found、NAME_UNKNOWN なら repository ... not found と分けています。つまり、Docker CLIがすべての404を権限エラーへ変換する実装ではありません。
それでも存在しない nosuchimage が DENIED になったのは、クライアントから見たRegistryの応答が権限拒否だったためです。認証の境界で存在を確認できなければ、クライアントは「本当にない」と「あるが見られない」を確定できません。その不確定さが、repository does not exist or may require 'docker login' という二者択一の文になっています。
この構造は、ビルド機能が増えた後には別の混乱も生みました。2025年のMobyの課題(Buildkit only wants to download images and refuse to use local images)では、ローカルにある基礎イメージを使う意図でも、docker-container driverのBuildKitがRegistryから解決しようとして、pull access denied になった例が報告されています。
また、2018年のDocker CLIの課題(Unable to use COPY –from, docker build trying to pull image)では、COPY --from の値が外部イメージとして解釈され、同じ文言が出ています。これらは、対象リポジトリの権限を直す問題ではありません。Registryへ取りに行くはずのない名前が、イメージ参照として解決されたことが原因です。
だから、このエラーを見たときの最初の問いは「ログインしたか」ではありません。Dockerは、どの文字列を、どのRegistryの、どのリポジトリとして取得しようとしたのかです。完全な参照名が正しいと確認できてから、存在と権限を分けます。
免責事項:本記事の内容は、執筆時点の公開情報をもとに作成したものです。ソフトウェアの仕様は予告なく変更されることがあります。最新の情報は各ツールの公式サポートページをご確認ください。本記事の情報を利用した結果生じたいかなる損害についても、著者および運営者は責任を負いかねます。
この記事でエラーは解決しましたか?