docker/cli · error

internal digest mismatch for

Error message

internal digest mismatch for %s: expected %s, got %s

What it means

Returned by `buildPutManifestRequest` (push.go:214-217) in the schema2 path. The CLI reconstructs the manifest bytes (using stored Raw, or re-marshaling with 3-space indent) and recomputes the digest; if it differs from the stored Descriptor.Digest, the internal storage is inconsistent and push is aborted to avoid uploading a manifest whose digest wouldn't match. This guards against the older storage format that didn't preserve raw bytes.

Solutions

  1. Re-create the manifest list with the current CLI so Raw bytes are preserved: delete and `docker manifest create` again.
  2. Clear the local manifest store and rebuild from registry sources.
  3. Ensure both create and push use the same (recent) CLI version.
  4. Annotate or amend the list to force re-serialization.

Example fix

# before
# list created by old CLI; push now fails digest check
docker manifest push mylist
# after
rm -rf ~/.local/share/docker/manifests/<list>
docker manifest create mylist img1 img2   # re-create with current CLI
docker manifest push mylist
Defensive patterns

Strategy: validation

Validate before calling

// before pushing, verify schema2 digest parity
for _, m := range manifests {
    if m.SchemaV2Manifest == nil { continue }
    dt := m.Raw
    if len(dt) == 0 { dt, _ = json.MarshalIndent(m.SchemaV2Manifest, "", "   ") }
    if d2 := m.Descriptor.Digest.Algorithm().FromBytes(dt); m.Descriptor.Digest != d2 {
        return fmt.Errorf("member %s digest mismatch; recreate the list with current CLI", m.Ref)
    }
}

Try / catch

if err := runPush(ctx, cli, opts); err != nil {
    if strings.Contains(err.Error(), "internal digest mismatch") {
        return recreateListAndPush(targetRef) // rebuild with preserved Raw bytes
    }
    return err
}

Prevention

When it happens

Trigger: Manifest list created by an older Docker CLI that didn't preserve `Raw` bytes, then pushed by a newer CLI — the re-marshaled indentation produces a different SHA. Also triggered by manual edits to the local manifest store.

Common situations: CLI upgrade across the storage-format boundary, or a corrupt local manifest cache.

Related errors


AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07). Data as JSON: /api/errors/240eebf5665cba19. Report an issue: GitHub.

Appendix: source

Thrown at cli/command/manifest/push.go:216

	// with the registry - if we haven't preserved the raw content.
	//
	// This is necessary because our previous internal storage format did not
	// preserve whitespace. If we don't have the newer format present, we can
	// attempt the reconstruction like before, but explicitly error if the
	// reconstruction failed!
	switch {
	case imageManifest.SchemaV2Manifest != nil:
		dt := imageManifest.Raw
		if len(dt) == 0 {
			dt, err = json.MarshalIndent(imageManifest.SchemaV2Manifest, "", "   ")
			if err != nil {
				return mountRequest{}, err
			}
		}

		dig := imageManifest.Descriptor.Digest
		if dig2 := dig.Algorithm().FromBytes(dt); dig != dig2 {
			return mountRequest{}, fmt.Errorf("internal digest mismatch for %s: expected %s, got %s", imageManifest.Ref, dig, dig2)
		}

		var manifest schema2.DeserializedManifest
		if err = manifest.UnmarshalJSON(dt); err != nil {
			return mountRequest{}, err
		}
		imageManifest.SchemaV2Manifest = &manifest
	case imageManifest.OCIManifest != nil:
		dt := imageManifest.Raw
		if len(dt) == 0 {
			dt, err = json.MarshalIndent(imageManifest.OCIManifest, "", "  ")
			if err != nil {
				return mountRequest{}, err
			}
		}

		dig := imageManifest.Descriptor.Digest
		if dig2 := dig.Algorithm().FromBytes(dt); dig != dig2 {

View on GitHub (pinned to 4f84911bfe)