containerd/containerd · error

media-type: expected manifest but found index (%s)

Error message

media-type: expected manifest but found index (%s)

What it means

validateMediaType returns "media-type: expected manifest but found index" when a blob whose descriptor says it is a manifest actually contains a manifests array or self-identifies as an index. This is a descriptor/blob mediaType contradiction caught during Children() traversal.

Source

Thrown at core/images/image.go:402

	Layers    json.RawMessage `json:"layers,omitempty"`
	Manifests json.RawMessage `json:"manifests,omitempty"`
	FSLayers  json.RawMessage `json:"fsLayers,omitempty"` // schema 1
}

// validateMediaType returns an error if the byte slice is invalid JSON,
// if the format of the blob is not supported, or if the media type
// identifies the blob as one format, but it identifies itself as, or
// contains elements of another format.
func validateMediaType(b []byte, mt string) error {
	var doc unknownDocument
	if err := json.Unmarshal(b, &doc); err != nil {
		return err
	}
	if len(doc.FSLayers) != 0 {
		return fmt.Errorf("media-type: schema 1 not supported")
	}
	if IsManifestType(mt) && (len(doc.Manifests) != 0 || IsIndexType(doc.MediaType)) {
		return fmt.Errorf("media-type: expected manifest but found index (%s)", mt)
	} else if IsIndexType(mt) && (len(doc.Config) != 0 || len(doc.Layers) != 0 || IsManifestType(doc.MediaType)) {
		return fmt.Errorf("media-type: expected index but found manifest (%s)", mt)
	}
	return nil
}

// RootFS returns the unpacked diffids that make up and images rootfs.
//
// These are used to verify that a set of layers unpacked to the expected
// values.
func RootFS(ctx context.Context, provider content.Provider, configDesc ocispec.Descriptor) ([]digest.Digest, error) {
	p, err := content.ReadBlob(ctx, provider, configDesc)
	if err != nil {
		return nil, err
	}

	var config ocispec.Image
	if err := json.Unmarshal(p, &config); err != nil {

View on GitHub (pinned to 4246446a2b)

Solutions

  1. Fix the mediaType on the descriptor to the index type (application/vnd.oci.image.index.v1+json or docker manifest list) when pushing multi-arch content.
  2. Verify the registry is returning the blob matching the requested digest (curl + jq the blob).
  3. Re-generate the image layout with an OCI-compliant tool (buildkit, oras, skopeo).

Example fix

// before: mislabeled descriptor
desc := ocispec.Descriptor{MediaType: ocispec.MediaTypeImageManifest, Digest: idxDigest}
// after
desc := ocispec.Descriptor{MediaType: ocispec.MediaTypeImageIndex, Digest: idxDigest}
Defensive patterns

Strategy: validation

Validate before calling

var probe struct{ Manifests json.RawMessage `json:"manifests"` }
// before treating desc as a manifest: ensure no manifests array present

Type guard

func looksLikeManifest(blob []byte) bool {
	var d struct{ Manifests json.RawMessage `json:"manifests"`; MediaType string `json:"mediaType"` }
	json.Unmarshal(blob, &d)
	return len(d.Manifests) == 0 && !images.IsIndexType(d.MediaType)
}

Try / catch

children, err := images.Children(ctx, store, desc)
if err != nil && strings.Contains(err.Error(), "expected manifest but found index") {
	return fixDescriptorMediaType(desc, ocispec.MediaTypeImageIndex)
}

Prevention

When it happens

Trigger: images.Children() on a descriptor whose MediaType is a manifest type (docker schema2 manifest or OCI manifest) but whose JSON body has a non-empty "manifests" field or internal mediaType of an index.

Common situations: Custom push code labeling a multi-arch index as a single-platform manifest; registries/mirrors serving the wrong blob for a digest; hand-crafted OCI layouts with copy-paste mediaType mistakes.

Related errors


AI-assisted analysis of containerd/containerd@4246446a2b (2026-09-02). Data as JSON: /api/errors/c34ee67c9c6942f6. Report an issue: GitHub.