Docker Container Image Reference Parser

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.

Paste below, or drop a file anywhere on this panel

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.

Examples

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.

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

An implicit :latest

A reference with no tag, which resolves to latest and is not reproducible

nginx
postgres

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Assuming the first path component is a registry

    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.

  2. Using a tag as if it were immutable

    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.

  3. Using a capital letter in a repository path

    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.

What it checks

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.

Whether the first part is a registry at all

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.

The implicit library/ namespace

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.

Tags that are not legal tags

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.

Uppercase 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.

Digests, character by character

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.

Ports, and the colon that is not a tag

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.

Every mistake here reports as the same error

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.

How a container image reference is actually read

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.

The grammar, and the parts that are usually missing

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

One rule decides whether you have a registry or a namespace

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

Docker Hub adds two things you did not write

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 name. A digest is the content.

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.

Why :latest is a reliability problem and not a style preference

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.

The registries that fix the shape of the path

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

More docker tools

Dockerfile Linter What this image will do to you later docker run to Kubernetes YAML And the flags that have no equivalent Docker Compose to Kubernetes And why depends_on has no equivalent Docker Logging Driver Generator json-file never rotates Dockerfile Non-Root USER Generator Numeric, or Kubernetes rejects it Docker OCI Image Labels Generator One of them actually does something Docker Hub Pull Rate Limit Calculator Counted per IP, not per person Docker Registry Storage Calculator Deleting a tag frees nothing Docker Build Context Analyzer All of it uploads before anything runs Docker Compose to docker run Converter Names stop resolving Docker Compose Version Migrator version: is obsolete now Docker Compose Network and Port Viewer Everything reaches everything Docker Command Explainer Which flags hand over the host Docker BuildKit Cache Mount Generator apt deletes the cache you just mounted Docker BuildKit Secret Mount Generator ARG is in docker history forever Docker .dockerignore Pattern Tester *.log does not match logs/app.log Docker Image Manifest Viewer unknown/unknown is not broken Docker Image Config Viewer Anyone who can pull can read your Env Docker Registry Token Decoder Check the scope before the credentials Docker HEALTHCHECK Generator Docker does nothing when it fails Docker Network Subnet Calculator 31 networks, then it stops Docker History Analyzer The rm did not save anything Docker Run to Compose Converter Some flags have no equivalent Docker CMD and ENTRYPOINT Tester Why docker stop takes ten seconds Docker Port Mapping Tester -p reaches past your firewall Docker -v to --mount Converter -v invents missing directories Docker Restart Policy Simulator The backoff resets after ten seconds Docker Exit Code Explainer 137 is two different problems Docker CPU Limit Converter --cpu-shares limits nothing Docker Memory Limit Calculator -m 512m allows a gigabyte Docker Compose Interpolation Tester An unset variable is an empty string Docker Environment Variable Precedence Tester Your shell does not reach the container Docker Compose Override Merge Previewer An override can add a port, never remove one Dockerfile Multi-Stage Build Analyzer Which stages reach the image Dockerignore Validator It is not .gitignore

Elsewhere on the site