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
| Error | Level | Direction |
|---|---|---|
| manifest unknown / not found | tag level | this post |
| pull access denied | repository level | pull access denied |
| no matching manifest for platform | architecture level | cause 4 here |
| exec format error | runtime architecture | align 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
- Character-by-character tag check (case, v prefix, separators)
- No tag written = pulling latest — does this image really have one?
- Hub Tags page / registry API for the real tag list
- Using
--platform? Confirm the tag has a manifest for it - Old Dockerfile image names: verify the repo wasn't renamed or retired
- Auth / rate-limit: does
docker loginchange the error? - 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.
Related Tools
Related Articles
Linux Crontab in Practice: Commands, Logs, and Debugging
Crontab beyond syntax: three must-know commands, /etc/cron.d vs user crontabs, five-step debugging, PATH/timezone/% escaping, MAILTO, and what to do when you need second-level scheduling.
docker run to docker-compose: The Complete Migration Guide (with Flag Map)
Migrate docker run commands to docker-compose.yml: five advantages of compose over raw run, a flag-by-flag mapping table (ports, volumes, env, restart, networks), the classic pitfalls (bare -e passthrough, anonymous volumes, --rm semantics), and an online converter that runs locally in your browser.
Permission denied (publickey): 6 Reasons Git Push Fails Over SSH (and the Fix for Each)
git clone or push rejected with Permission denied (publickey) fatal: Could not read from remote repository? Covers missing keygen, key not loaded in the agent, public key not added to GitHub/GitLab, multi-account key routing with ~/.ssh/config, deploy key limits, and wrong remote URLs — with ssh -v diagnostics.