amir20/dozzle · error

invalid image reference

Error message

invalid image reference %q: empty digest

What it means

When the reference contains '@', everything after it is the digest; if it is empty (reference ends with '@'), ParseReference returns this error because a digest-qualified reference must carry a digest value.

Solutions

  1. Remove the trailing '@' if you did not intend digest pinning
  2. Provide the full digest, e.g. nginx:1.25@sha256:<64 hex chars>
  3. Guard the string building code so the digest is only appended when non-empty

Example fix

// before
ref := "nginx:1.25@" + digest // digest may be ""
// after
var ref string
if digest != "" {
    ref = "nginx:1.25@" + digest
} else {
    ref = "nginx:1.25"
}
Defensive patterns

Strategy: validation

Validate before calling

if strings.HasSuffix(ref, "@") {
    return fmt.Errorf("reference %q ends with '@' but has no digest", ref)
}

Type guard

func isDigestPinned(ref string) bool {
    i := strings.Index(ref, "@")
    return i != -1 && i < len(ref)-1
}

Try / catch

if _, err := imagecheck.ParseReference(ref); err != nil {
    if strings.Contains(err.Error(), "empty digest") {
        ref = strings.TrimSuffix(ref, "@")
    }
}

Prevention

When it happens

Trigger: Passing references like "nginx@", "nginx:1.25@", or "repo:5000/img@" where the '@' delimiter exists but nothing follows it.

Common situations: String concatenation that appends '@' plus a digest fetched asynchronously but failed, templates that render '@{{digest}}' as bare '@', copy-paste truncation of a digest-pinned image name.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07). Data as JSON: /api/errors/30d7c01b17cd6ae7. Report an issue: GitHub.

Appendix: source

Thrown at internal/imagecheck/reference.go:101

	return fmt.Sprintf("%s://%s/v2/%s/manifests/%s", r.scheme(), r.host(), r.Repository, target)
}

// ParseReference splits an image reference into its registry, repository and
// tag/digest parts, applying Docker's implicit defaults.
func ParseReference(ref string) (Reference, error) {
	if ref == "" {
		return Reference{}, fmt.Errorf("empty image reference")
	}

	remainder := ref
	var digest string
	// A digest always trails the reference and may follow a tag, as in
	// "nginx:1.25@sha256:abc...".
	if i := strings.Index(remainder, "@"); i != -1 {
		digest = remainder[i+1:]
		remainder = remainder[:i]
		if digest == "" {
			return Reference{}, fmt.Errorf("invalid image reference %q: empty digest", ref)
		}
	}

	registry := defaultRegistry
	// The first path component is a registry only when it looks like a host:
	// it contains a dot or port separator, or is localhost. Otherwise it is a
	// Docker Hub namespace such as "amir20" in "amir20/dozzle".
	if i := strings.Index(remainder, "/"); i != -1 {
		candidate := remainder[:i]
		if candidate == "localhost" || strings.ContainsAny(candidate, ".:") {
			registry = candidate
			remainder = remainder[i+1:]
		}
	}

	// Docker Hub answers to several names. They have to collapse to one, or a
	// reference written as index.docker.io/library/nginx never lines up with
	// the "nginx@sha256:..." that Docker records locally.

View on GitHub (pinned to d9463cbe21)