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
- Re-create the manifest list with the current CLI so Raw bytes are preserved: delete and `docker manifest create` again.
- Clear the local manifest store and rebuild from registry sources.
- Ensure both create and push use the same (recent) CLI version.
- 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
- Use the same recent CLI version for create and push.
- Recreate old lists to preserve Raw bytes.
- Never manually edit the local manifest store.
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
- digest parse of image
- failed to assemble ManifestDescriptor
- not found
- manifest must have an OS and Architecture to be pushed to a…
- cannot use source images from a different registry than the…
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)