containerd/containerd · error

media-type: schema 1 not supported

Error message

media-type: schema 1 not supported

What it means

validateMediaType rejects blobs containing an fsLayers field with "media-type: schema 1 not supported". Docker schema 1 manifests (pre-2017 format) are explicitly unsupported by containerd's image walk/children traversal.

Source

Thrown at core/images/image.go:399

type unknownDocument struct {
	MediaType string          `json:"mediaType,omitempty"`
	Config    json.RawMessage `json:"config,omitempty"`
	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
	}

View on GitHub (pinned to 4246446a2b)

Solutions

  1. Rebuild/re-push the image so it produces a schema 2 or OCI manifest (docker build + modern push, or buildkit).
  2. Use `docker save`/`skopeo copy --format oci` to convert legacy image tarballs before importing.
  3. If the registry still serves schema 1, configure the Docker client to request schema 2 (DOCKER_NOW_TLS... no — set Accept headers via a modern puller) or upgrade the registry.
  4. Migrate legacy registries with skopeo sync to regenerate manifest format.

Example fix

// before: pulling legacy image directly
ctr images pull oldreg/app:1.0 // fails with schema 1 error
// after: convert first
// skopeo copy --format oci docker://oldreg/app:1.0 oci:/tmp/app:1.0
// ctr images import /tmp/app/oci-layout
Defensive patterns

Strategy: validation

Validate before calling

ra, _ := cs.ReaderAt(ctx, desc); blob := make([]byte, ra.Size()); ra.ReadAt(blob, 0); if bytes.Contains(blob, []byte("fsLayers")) { return errors.New("schema 1 image; migrate first") }

Type guard

func isSchema1(desc ocispec.Descriptor) bool {
	return desc.MediaType == images.MediaTypeDockerSchema1Manifest
}

Try / catch

_, err := images.Children(ctx, store, desc)
if err != nil && strings.Contains(err.Error(), "schema 1 not supported") {
	return migrateLegacyImage(ctx, ref) // skopeo copy --format oci
}

Prevention

When it happens

Trigger: Walking/fetching an image whose blob was pushed as Docker schema 1 (application/vnd.docker.distribution.manifest.v1+prettyjws) while traversed via Children()/validateMediaType.

Common situations: Images from legacy private registries (old Harbor/docker registry) never re-pushed; cached blobs from pre-2017 images; vendored image tarballs exported from very old Docker daemons.

Related errors


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