Skip to content
DevOps2026-09-086 min read

docker manifest unknown: 4 Reasons the Tag Can't Be Found

Symptom: the repo exists, the tag doesn't

Error response from daemon: manifest for myapp/api:v1.2.0 not found:
manifest unknown: manifest unknown

One-line diagnosis: the registry knows the repository, but can't find a manifest for the tag you wrote. A manifest is the image's table of contents (listing layers per architecture) and a tag points to one — wrong tag, or pointing at nothing, produces this error.

It's the sibling of pull access denied: denied is a repository-level problem (missing/no permission), manifest unknown is a tag-level problem inside the repo. Seeing manifest unknown usually means the repo name is right — focus on the tag.

Four causes, ranked by frequency

1. Tag typo (most common)

Case-sensitive, v1.2 and 1.2 are different tags, and -/_ look alike. The reliable check — ask the registry directly:

# List an image's tags (Docker Hub)
curl -s "https://hub.docker.com/v2/repositories/library/nginx/tags?page_size=20" | grep -o '"name":"[^"]*"'

# Or verify a specific manifest exists
docker manifest inspect myapp/api:v1.2.0

2. The latest trap: the image has no latest tag

docker pull myapp/api with no tag defaults to latest. Official images maintain it, but personal/team images frequently only pushed version tags — docker push myapp/api:v1.2.0 does not create latest. Spell the tag out:

docker pull myapp/api:v1.2.0   # ✅ explicit tag
docker pull myapp/api          # ❌ pulls a latest that may not exist

3. The official image was renamed or retired

The ecosystem moves: repositories transfer (old ones archived read-only), get renamed, or prune version tags. If your Dockerfile/docs are a few years old, verify with docker manifest inspect, then search Hub for the new home.

4. Architecture mismatch: the manifest exists, but not for your platform

With docker pull --platform linux/amd64 myapp/api:v1, if that tag was only ever pushed for arm64, you get manifest unknown or no matching manifest. Frequent for cross-builds from M-series Macs:

# Which platforms does this tag have?
docker manifest inspect myapp/api:v1 | grep -A2 platform

# Build explicitly for the target platform
docker buildx build --platform linux/amd64 -t myapp/api:v1 --push .

exec format error at runtime is what happens when this mismatch slips past the pull stage — align platforms at pull time.

The triage flow (two minutes, in order)

# 1. List the tags that actually exist
curl -s "https://hub.docker.com/v2/repositories/<user>/<repo>/tags?page_size=50" | grep -o '"name":"[^"]*"'

# 2. Tag looks right but still failing? Check manifest and architecture
docker manifest inspect <full-image-name:tag>

# 3. Pulled without a tag? Add the explicit tag and retry
docker pull <full-image-name>:<explicit-tag>

Telling the neighboring errors apart

ErrorLevelDirection
manifest unknown / not foundtag levelthis post
pull access deniedrepository levelpull access denied
no matching manifest for platformarchitecture levelcause 4 here
exec format errorruntime architecturealign platforms at pull time

Inspecting what the registry actually holds

Two commands resolve most manifest disputes without pulling. docker manifest inspect name:tag (needs experimental CLI on older versions) prints the manifest list — a multi-arch image shows entries for amd64/arm64; a single-arch image shows one. If the list lacks your platform, the pull fails with exactly this error on the wrong-architecture machine (the classic case: built on Apple Silicon, deployed to amd64 CI). skopeo inspect docker://name:tag gives the same answer without any Docker daemon and works in CI where the daemon socket is unavailable. Checking before pulling turns a cryptic runtime error into a ten-second read.

Buildx: making the mismatch impossible

If you build and ship images, the fix is build-time: docker buildx build --platform linux/amd64,linux/arm64 --push produces a manifest list that answers both platforms, and the error class disappears for every consumer. The cost is a QEMU setup for cross-building (docker run --privileged multiarch/qemu-user-static once per host). Teams that deploy from laptops: buildx is not optional tooling — it is the difference between "works on the server" and "works where it was built".

Registry quirks worth knowing

Docker Hub rate-limits anonymous manifest fetches (401/429 masquerading as manifest failures at高峰); GitHub Container Registry requires the owner-case-exact name (ghcr.io/Owner/repo fails if you type owner); self-hosted registries behind proxies sometimes serve a stale manifest cache after a push (retry or bust the proxy). When the same tag pulls fine on one machine and fails on another, the machine's DNS/proxy path is the suspect, not the tag.

Tag vs digest: pin what you actually pulled

Tags move. myapp/api:v1 can point at a different manifest tomorrow after a rebuild+push. Digests do not:

# Resolve tag → immutable digest
docker buildx imagetools inspect myapp/api:v1.2.0 --format '{{json .Manifest}}'

# Pull / FROM by digest (sha256:…)
docker pull myapp/api@sha256:0123abcd…

In a Dockerfile, prefer FROM myapp/api:v1.2.0@sha256:… (tag for humans, digest for reproducibility) or generate a pinned base image with the Dockerfile Generator. Compose files that only pin floating tags recreate the same “works on CI Friday, fails Monday” class of failures as missing tags — the error may still say manifest unknown if someone deleted the old digest’s tag mapping from the registry UI.

Auth noise that looks like “tag missing”

Private registries sometimes return 401/403 that clients rewrite into manifest unknown or not found to avoid leaking whether a repo exists. Clues:

  • The same tag pulls fine after docker login / a fresh CI token.
  • Anonymous Hub pulls hit rate limits (429) that surface as confusing pull errors at peak hours.
  • GHCR and many cloud registries require the exact owner casing in the path; a lowercased org name can look identical to a missing tag.

If docker manifest inspect fails with auth language but Hub’s Tags page shows the tag in a browser where you are logged in, fix credentials first — do not burn time on typo hunts.

Compose and CI: where the wrong tag enters

image: myapp/api with no tag, ${IMAGE_TAG} unset (empty string), or a workflow that builds :sha-abc but deploys :latest are the three most common pipelines that manufacture this error after a green build. Print the resolved reference before pull:

echo "pulling ${IMAGE:?IMAGE unset}"
docker compose config | grep 'image:'
docker pull "$IMAGE"

When local docker pull works but the remote agent fails, compare daemon architecture (docker version Client/Server OS/Arch) and registry mirror config — mirrors can cache a stale or single-arch manifest list.

Checklist

  1. Character-by-character tag check (case, v prefix, separators)
  2. No tag written = pulling latest — does this image really have one?
  3. Hub Tags page / registry API for the real tag list
  4. Using --platform? Confirm the tag has a manifest for it
  5. Old Dockerfile image names: verify the repo wasn't renamed or retired
  6. Auth / rate-limit: does docker login change the error?
  7. Prefer digest pins for production FROM/pull lines after the tag resolves

Triage notes

The triage main line watches the tag, not the repository name, per the sibling-error split in the intro: pull access denied is repository-level, manifest unknown is tag-level — by the time you see manifest unknown, the repository name is usually already correct. Among the four causes, tag typos rank first and the latest trap second, because pushing a version tag does not auto-create latest, and an omitted tag defaults to pulling exactly that nonexistent label. The closing "auth noise" section is deliberate: the tell is that the same tag pulls immediately after docker login — fix credentials first instead of spinning on spelling checks.


Provided by ToolVault. Related tools: Dockerfile Generator, Run to Compose, Linux Cheatsheet. Related reading: pull access denied, Docker disk cleanup. See the homepage for more developer tools.