Registry or Docker Hub user
myregistry/app is a Hub namespace and myregistry.io/app is a registry, decided by the dot
myregistry/app:1.0 myregistry.io/app:1.0 nginx
Paste image references, or the manifest they live in, and see what each one actually resolves to: the registry, the namespace, the tag and the digest, with every implicit default filled in. Including the rule that decides whether the first component is your registry or somebody else's Docker Hub account.
Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.
The answer appears here
Paste on the left and press Parse. Nothing leaves this tab.
Nothing else to flag.
No formatting problems, and nothing the rules object to. Worth remembering what that covers: this reads the file you pasted, not the account or cluster it will be applied to.
No finding matches that filter.
Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.
myregistry/app is a Hub namespace and myregistry.io/app is a registry, decided by the dot
myregistry/app:1.0 myregistry.io/app:1.0 nginx
A reference with no tag, which resolves to latest and is not reproducible
nginx postgres
These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.
It is only a registry if it contains a dot or a colon, or is exactly localhost. So myregistry/app resolves to Docker Hub and myregistry.io/app does not.
Instead:Add a dot, or write the registry explicitly.
A tag is a pointer and can be moved. Two pulls of the same tag on different days can give different images, which defeats a rollback.
Instead:Pin by digest for anything reproducible. The tag stays for humans.
Path components must be lowercase. A capital is rejected by the registry with an error that does not mention case.
Instead:Lowercase the whole path. Only the tag allows mixed case.
Splitting a reference on slashes and colons is easy. The useful part is knowing which fields were filled in for you, because every one of them changes what gets pulled and none of them appear in the string.
myregistry/app is docker.io/myregistry/app. myregistry.io/app is a registry called myregistry.io. The rule is that the first component is a host only when it contains a dot or a colon, or is exactly localhost, and nothing in the string says which case you are in. Every reference is shown fully qualified, so the answer is not a guess.
A single-component name on Docker Hub is an official image: nginx is docker.io/library/nginx:latest, published by Docker rather than by you. The resolved form spells that out, which matters when the name you meant was your own copy of it.
A tag is [a-zA-Z0-9_] then up to 127 of [a-zA-Z0-9._-]. Semver build metadata uses a plus sign, which is not in that set, so 1.0.0+build1 is rejected on push and CI writes it constantly. Leading dots and leading hyphens are the other two, and uppercase is fine in a tag even though it is not in a repository name.
Repository paths are lowercase only. An uppercase letter does not produce a message about a bad name, it produces a name that does not exist, so the failure reads exactly like a missing image or a repository you have no access to. GitHub owner names allow uppercase and GHCR does not, which is where this bites most often.
sha256 followed by exactly 64 lowercase hex characters. The length and the alphabet are both checked, so a truncated paste is named as one, and a 12-character value is identified as the short form of a local image ID, which is the image config digest and can never appear in a reference.
localhost:5000/app is a registry on port 5000, not a repository with the tag 5000. The parser decides by position: a colon after the last slash is a tag, a colon before it is a port. It also catches a host pasted with no repository after it, which Docker reads as a Docker Hub image with a numeric tag.
A registry has no way to tell you that you meant a different registry. An uppercase letter, a missing dot in the hostname, a namespace that should have been a host: all three produce a request for a repository that does not exist, and the answer is a 404 that the client reports as the image not being found or as authorisation being required.
That is the same message you get for a private repository you have no credentials for, and for a tag that was never pushed. So the first hour goes on credentials and the registry's permissions, and the actual cause is a character in the name.
Resolving the reference the way the client resolves it, and printing what came out, ends that search before it starts. Nothing here is sent anywhere: the parse runs in this tab, and the registry is never contacted, so this says what the reference means and not whether the image exists.
A reference looks like a URL and is not one. It has no scheme, its host is optional, and most of what it means comes from defaults that are never written down in the string. Two references that look almost identical can point at different registries, and the difference between them is one dot.
A full reference is a registry with an optional port, a namespace, a repository, and then either a tag or a digest or both. Only the repository is required. Everything else is filled in by the client before the request is made, which is why a reference that fails looks nothing like the name in the error message.
[REGISTRY[:PORT]/]NAMESPACE/REPOSITORY[:TAG][@DIGEST]
ghcr.io/acme/api:v2
| | | |
| | | +-- tag v2
| | +------ repository api
| +----------- namespace acme
+------------------- registry ghcr.io
nginx
+------------------- everything else is defaulted:
docker.io/library/nginx:latest The first path component is treated as a registry host only if it contains a dot or a colon, or is exactly the word localhost. Otherwise it is a namespace on Docker Hub. This is the single most expensive detail here, because a private registry named without its domain does not fail as a configuration error: it resolves to a Docker Hub repository that does not exist, and the error says the image cannot be found.
myregistry/app -> docker.io/myregistry/app:latest
myregistry.io/app -> myregistry.io/app:latest (a dot)
myregistry:5000/app -> myregistry:5000/app:latest (a colon)
localhost/app -> localhost/app:latest (the exception)
registry.local/app -> registry.local/app:latest
The port case is decided by position, not by content:
localhost:5000/app the colon is before the last "/", so 5000 is a port
alpine:5000 the colon is after it, so 5000 is a tag An absent registry becomes docker.io. A single-component name then gets an implicit library/ namespace, which is where Docker's official images live. So nginx is not a name local to anything: it is docker.io/library/nginx, published by Docker. Anonymous pulls from Docker Hub are also rate limited by client IP, which is a common cause of ImagePullBackOff on a cluster that scales up.
nginx -> docker.io/library/nginx:latest
bitnami/nginx -> docker.io/bitnami/nginx:latest
docker.io/nginx -> docker.io/library/nginx:latest
index.docker.io and registry-1.docker.io are the same
registry under its API hostnames. A tag is a label the registry keeps pointing at a manifest, and anyone with push access can move it to a different one at any time. A digest is the hash of the manifest, so it addresses the bytes directly and cannot be repointed. When both are written, the digest wins and the tag is decoration, which is worth knowing before editing the tag on a line that has both and expecting a different image to run.
api:v2 a name. It can move.
api@sha256:9f2a... the content. It cannot.
api:v2@sha256:9f2a... the digest is pulled, v2 is a comment
Two caveats on digests:
the digest of a multi-architecture index is not the digest
of the per-platform manifest inside it, and
a registry that prunes untagged manifests can delete content
that only a digest still refers to. Repository path components are lowercase only, made of alphanumeric runs joined by a single dot, a single underscore, a double underscore, or hyphens, and they can neither start nor end with a separator. Tags are the other way round: uppercase is allowed, but the first character has to be a letter, a digit or an underscore, and the plus sign that semantic versioning uses for build metadata is not a legal character anywhere in a tag.
repository tag
MyOrg/app invalid v1.2.3 valid
my_org/app valid V1.2-Beta valid (case is fine here)
my-org/app valid 1.0.0+build1 INVALID (the "+")
-myorg/app invalid .1.0 INVALID (leading dot)
myorg/app- invalid -rc1 INVALID (leading hyphen)
1.0.0+build1 is what a CI job writes when it tags straight
from a semver string. Use 1.0.0_build1 or 1.0.0-build1. Kubernetes defaults imagePullPolicy from the tag: latest, or no tag at all, becomes Always, and any other tag becomes IfNotPresent. That coupling produces both failure modes. With latest, two replicas of one Deployment started a minute apart can resolve to two different builds, the running version is not knowable from the manifest, and there is no previous version recorded to roll back to. With a fixed tag that somebody moves, the opposite happens: nodes that already have the tag cached keep running the old image, and the cluster ends up split between two builds with nothing in the manifest showing it.
image: api -> :latest -> pullPolicy Always
image: api:latest -> latest -> pullPolicy Always
image: api:v2 -> v2 -> pullPolicy IfNotPresent
image: api@sha256:... -> no tag -> pullPolicy IfNotPresent
image: api:latest@sha256:... -> pullPolicy Always
The last line is the odd one: the content is pinned by digest,
but the default is read from the tag, so it is still Always. Most private registries take any path. Four common ones do not, and a short path there is not a shorter name, it is a name that does not exist. ECR is the one with a further consequence: the account id and the region are inside the hostname, so an ECR reference is specific to one environment and a manifest promoted between accounts or regions points at a registry the new cluster may have no permission on.
123456789012.dkr.ecr.eu-west-1.amazonaws.com/team/api:v2
| | |
| | +-- region, in the hostname
| +---------- always 12 digits
+----------------------- the AWS account that owns the registry
public.ecr.aws/REGISTRY-ALIAS/IMAGE
ghcr.io/OWNER/IMAGE owner must be lowercase
quay.io/ORGANISATION/IMAGE
gcr.io/PROJECT/IMAGE superseded by:
REGION-docker.pkg.dev/PROJECT/REPO/IMAGE a repository level is added