diff --git a/lib/modules/datasource/docker/index.ts b/lib/modules/datasource/docker/index.ts
index a349ce3ac66604ca710b0f328df651ba7a21efff..24f842558ffdb178a2a6c48ad95d55fcf135b5db 100644
--- a/lib/modules/datasource/docker/index.ts
+++ b/lib/modules/datasource/docker/index.ts
@@ -29,13 +29,7 @@ import {
   sourceLabels,
 } from './common';
 import { ecrPublicRegex, ecrRegex, isECRMaxResultsError } from './ecr';
-import type {
-  Image,
-  ImageConfig,
-  ImageList,
-  OciImage,
-  OciImageList,
-} from './types';
+import type { Manifest, OciImageConfig } from './schema';
 
 const defaultConfig = {
   commitMessageTopic: '{{{depName}}} Docker tag',
@@ -170,7 +164,7 @@ export class DockerDatasource extends Datasource {
     registryHost: string,
     dockerRepository: string,
     configDigest: string
-  ): Promise<HttpResponse<ImageConfig> | undefined> {
+  ): Promise<HttpResponse<OciImageConfig> | undefined> {
     logger.trace(
       `getImageConfig(${registryHost}, ${dockerRepository}, ${configDigest})`
     );
@@ -192,7 +186,7 @@ export class DockerDatasource extends Datasource {
       'blobs',
       configDigest
     );
-    return await this.http.getJson<ImageConfig>(url, {
+    return await this.http.getJson<OciImageConfig>(url, {
       headers,
       noAuth: true,
     });
@@ -215,11 +209,8 @@ export class DockerDatasource extends Datasource {
     if (!manifestResponse) {
       return null;
     }
-    const manifest = JSON.parse(manifestResponse.body) as
-      | ImageList
-      | Image
-      | OciImageList
-      | OciImage;
+    // TODO: validate schema
+    const manifest = JSON.parse(manifestResponse.body) as Manifest;
     if (manifest.schemaVersion !== 2) {
       logger.debug(
         { registry, dockerRepository, tag },
@@ -398,10 +389,10 @@ export class DockerDatasource extends Datasource {
     registryHost: string,
     dockerRepository: string,
     tag: string
-  ): Promise<Record<string, string>> {
+  ): Promise<Record<string, string> | undefined> {
     logger.debug(`getLabels(${registryHost}, ${dockerRepository}, ${tag})`);
     try {
-      let labels: Record<string, string> = {};
+      let labels: Record<string, string> | undefined = {};
       const configDigest = await this.getConfigDigest(
         registryHost,
         dockerRepository,
@@ -427,7 +418,8 @@ export class DockerDatasource extends Datasource {
         noAuth: true,
       });
 
-      const body = JSON.parse(configResponse.body);
+      // TODO: validate schema
+      const body = JSON.parse(configResponse.body) as OciImageConfig;
       if (body.config) {
         labels = body.config.Labels;
       } else {
@@ -765,11 +757,8 @@ export class DockerDatasource extends Datasource {
         );
 
         if (architecture && manifestResponse) {
-          const manifestList = JSON.parse(manifestResponse.body) as
-            | ImageList
-            | Image
-            | OciImageList
-            | OciImage;
+          // TODO: validate Schema
+          const manifestList = JSON.parse(manifestResponse.body) as Manifest;
           if (
             manifestList.schemaVersion === 2 &&
             (manifestList.mediaType ===
@@ -779,7 +768,7 @@ export class DockerDatasource extends Datasource {
               (!manifestList.mediaType && 'manifests' in manifestList))
           ) {
             for (const manifest of manifestList.manifests) {
-              if (manifest.platform['architecture'] === architecture) {
+              if (manifest.platform?.architecture === architecture) {
                 digest = manifest.digest;
                 break;
               }
diff --git a/lib/modules/datasource/docker/schema.spec.ts b/lib/modules/datasource/docker/schema.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f2cabc659a1e7cfed3a1f2fa649740c5b97478c2
--- /dev/null
+++ b/lib/modules/datasource/docker/schema.spec.ts
@@ -0,0 +1,283 @@
+import {
+  DistributionListManifest,
+  DistributionManifest,
+  HelmConfigBlob,
+  Manifest,
+  OciImageIndexManifest,
+  OciImageManifest,
+} from './schema';
+
+describe('modules/datasource/docker/schema', () => {
+  it('parses OCI image manifest', () => {
+    const manifest = {
+      schemaVersion: 2,
+      mediaType: 'application/vnd.oci.image.manifest.v1+json',
+      config: {
+        mediaType: 'application/vnd.oci.image.config.v1+json',
+        digest: 'sha256:1234567890abcdef',
+        size: 12345,
+      },
+      layers: [
+        {
+          mediaType: 'application/vnd.oci.image.layer.v1.tar+gzip',
+          digest: 'sha256:1234567890abcdef',
+          size: 12345,
+        },
+      ],
+    };
+    expect(OciImageManifest.parse(manifest)).toMatchObject({
+      schemaVersion: 2,
+      mediaType: 'application/vnd.oci.image.manifest.v1+json',
+      config: {
+        mediaType: 'application/vnd.oci.image.config.v1+json',
+        digest: 'sha256:1234567890abcdef',
+        size: 12345,
+      },
+    });
+
+    expect(Manifest.parse(manifest)).toMatchObject({
+      schemaVersion: 2,
+      mediaType: 'application/vnd.oci.image.manifest.v1+json',
+    });
+
+    delete (manifest as any).mediaType;
+
+    expect(OciImageManifest.parse(manifest)).toMatchObject({
+      schemaVersion: 2,
+      mediaType: 'application/vnd.oci.image.manifest.v1+json',
+      config: {
+        mediaType: 'application/vnd.oci.image.config.v1+json',
+        digest: 'sha256:1234567890abcdef',
+        size: 12345,
+      },
+    });
+  });
+
+  it('parses OCI helm manifest', () => {
+    const manifest = {
+      schemaVersion: 2,
+      mediaType: 'application/vnd.oci.image.manifest.v1+json',
+      config: {
+        mediaType: 'application/vnd.cncf.helm.config.v1+json',
+        digest:
+          'sha256:179618538d9f341897e5e05b2997f8c6f3009afe825525e87cf38a877fcb028e',
+        size: 1066,
+      },
+      layers: [
+        {
+          mediaType: 'application/vnd.cncf.helm.chart.content.v1.tar+gzip',
+          digest:
+            'sha256:0dc5d782d04596548f91d54a412013d844baabede2daa7ec9e201e9c80daf533',
+          size: 234103,
+        },
+      ],
+    };
+    expect(OciImageManifest.parse(manifest)).toMatchObject({
+      schemaVersion: 2,
+      mediaType: 'application/vnd.oci.image.manifest.v1+json',
+      config: {
+        mediaType: 'application/vnd.cncf.helm.config.v1+json',
+        digest:
+          'sha256:179618538d9f341897e5e05b2997f8c6f3009afe825525e87cf38a877fcb028e',
+        size: 1066,
+      },
+    });
+
+    expect(Manifest.parse(manifest)).toMatchObject({
+      schemaVersion: 2,
+      mediaType: 'application/vnd.oci.image.manifest.v1+json',
+    });
+
+    delete (manifest as any).mediaType;
+
+    expect(OciImageManifest.parse(manifest)).toMatchObject({
+      schemaVersion: 2,
+      mediaType: 'application/vnd.oci.image.manifest.v1+json',
+      config: {
+        mediaType: 'application/vnd.cncf.helm.config.v1+json',
+        digest:
+          'sha256:179618538d9f341897e5e05b2997f8c6f3009afe825525e87cf38a877fcb028e',
+        size: 1066,
+      },
+    });
+  });
+
+  it('parses OCI image index', () => {
+    const manifest = {
+      schemaVersion: 2,
+      mediaType: 'application/vnd.oci.image.index.v1+json',
+      manifests: [
+        {
+          mediaType: 'application/vnd.oci.image.manifest.v1+json',
+          size: 7143,
+          digest:
+            'sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f',
+          platform: {
+            architecture: 'ppc64le',
+            os: 'linux',
+          },
+        },
+        {
+          mediaType: 'application/vnd.oci.image.manifest.v1+json',
+          size: 7682,
+          digest:
+            'sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270',
+          platform: {
+            architecture: 'amd64',
+            os: 'linux',
+          },
+        },
+      ],
+      annotations: {
+        'com.example.key1': 'value1',
+        'com.example.key2': 'value2',
+      },
+    };
+
+    expect(OciImageIndexManifest.parse(manifest)).toMatchObject({
+      schemaVersion: 2,
+      mediaType: 'application/vnd.oci.image.index.v1+json',
+    });
+
+    expect(Manifest.parse(manifest)).toMatchObject({
+      schemaVersion: 2,
+      mediaType: 'application/vnd.oci.image.index.v1+json',
+    });
+
+    delete (manifest as any).mediaType;
+    expect(OciImageIndexManifest.parse(manifest)).toMatchObject({
+      schemaVersion: 2,
+      mediaType: 'application/vnd.oci.image.index.v1+json',
+    });
+  });
+
+  it('parses distribution manifest', () => {
+    const manifest = {
+      schemaVersion: 2,
+      mediaType: 'application/vnd.docker.distribution.manifest.v2+json',
+      config: {
+        mediaType: 'application/vnd.docker.container.image.v1+json',
+        digest:
+          'sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7',
+        size: 7023,
+      },
+      layers: [
+        {
+          mediaType: 'application/vnd.docker.image.rootfs.diff.tar.gzip',
+          digest:
+            'sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f',
+          size: 32654,
+        },
+        {
+          mediaType: 'application/vnd.docker.image.rootfs.diff.tar.gzip',
+          digest:
+            'sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b',
+          size: 16724,
+        },
+        {
+          mediaType: 'application/vnd.docker.image.rootfs.diff.tar.gzip',
+          digest:
+            'sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736',
+          size: 73109,
+        },
+      ],
+    };
+
+    expect(DistributionManifest.parse(manifest)).toMatchObject({
+      schemaVersion: 2,
+      mediaType: 'application/vnd.docker.distribution.manifest.v2+json',
+    });
+
+    expect(Manifest.parse(manifest)).toMatchObject({
+      schemaVersion: 2,
+      mediaType: 'application/vnd.docker.distribution.manifest.v2+json',
+    });
+  });
+
+  it('parses distribution manifest list', () => {
+    const manifest = {
+      schemaVersion: 2,
+      mediaType: 'application/vnd.docker.distribution.manifest.list.v2+json',
+      manifests: [
+        {
+          mediaType: 'application/vnd.docker.distribution.manifest.v2+json',
+          digest:
+            'sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f',
+          size: 7143,
+          platform: {
+            architecture: 'ppc64le',
+            os: 'linux',
+          },
+        },
+        {
+          mediaType: 'application/vnd.docker.distribution.manifest.v2+json',
+          digest:
+            'sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270',
+          size: 7682,
+          platform: {
+            architecture: 'amd64',
+            os: 'linux',
+            features: ['sse4'],
+          },
+        },
+      ],
+    };
+
+    expect(DistributionListManifest.parse(manifest)).toMatchObject({
+      schemaVersion: 2,
+      mediaType: 'application/vnd.docker.distribution.manifest.list.v2+json',
+    });
+
+    expect(Manifest.parse(manifest)).toMatchObject({
+      schemaVersion: 2,
+      mediaType: 'application/vnd.docker.distribution.manifest.list.v2+json',
+    });
+  });
+
+  it('parses OCI helm chart config', () => {
+    const manifest = {
+      annotations: {
+        category: 'Infrastructure',
+        licenses: 'Apache-2.0',
+      },
+      apiVersion: 'v2',
+      appVersion: '2.8.2',
+      dependencies: [
+        {
+          condition: 'redis.enabled',
+          name: 'redis',
+          repository: 'oci://registry-1.docker.io/bitnamicharts',
+          version: '17.x.x',
+        },
+        {
+          condition: 'postgresql.enabled',
+          name: 'postgresql',
+          repository: 'oci://registry-1.docker.io/bitnamicharts',
+          version: '12.x.x',
+        },
+        {
+          name: 'common',
+          repository: 'oci://registry-1.docker.io/bitnamicharts',
+          tags: ['bitnami-common'],
+          version: '2.x.x',
+        },
+      ],
+      home: 'https://bitnami.com',
+      icon: 'https://bitnami.com/assets/stacks/harbor-core/img/harbor-core-stack-220x234.png',
+      keywords: ['docker', 'registry', 'vulnerability', 'scan'],
+      maintainers: [
+        {
+          name: 'VMware, Inc.',
+          url: 'https://github.com/bitnami/charts',
+        },
+      ],
+      name: 'harbor',
+      sources: ['https://github.com/bitnami/charts/tree/main/bitnami/harbor'],
+      version: '16.7.2',
+    };
+
+    expect(HelmConfigBlob.parse(manifest)).toMatchObject({
+      sources: ['https://github.com/bitnami/charts/tree/main/bitnami/harbor'],
+    });
+  });
+});
diff --git a/lib/modules/datasource/docker/schema.ts b/lib/modules/datasource/docker/schema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..43e51b0cfe51b68f2d602ad1574fc389eee6fa60
--- /dev/null
+++ b/lib/modules/datasource/docker/schema.ts
@@ -0,0 +1,129 @@
+import { z } from 'zod';
+
+// Helm manifests
+export const HelmConfigBlob = z.object({
+  home: z.string().optional(),
+  sources: z.array(z.string()).optional(),
+});
+
+// OCI manifests
+
+/**
+ *  OCI manifest object
+ */
+export const ManifestObject = z.object({
+  schemaVersion: z.literal(2),
+  mediaType: z.string(),
+});
+
+/**
+ * Oci descriptor
+ * https://github.com/opencontainers/image-spec/blob/main/descriptor.md
+ */
+export const Descriptor = z.object({
+  mediaType: z.string(),
+  digest: z.string(),
+  size: z.number(),
+});
+/**
+ * OCI platform properties
+ * https://github.com/opencontainers/image-spec/blob/main/image-index.md
+ */
+const OciPlatform = z
+  .object({
+    architecture: z.string().optional(),
+  })
+  .optional();
+
+/**
+ * OCI Image Configuration.
+ *
+ * Compatible with old docker configiguration.
+ https://github.com/opencontainers/image-spec/blob/main/config.md
+ */
+export const OciImageConfig = z.object({
+  architecture: z.string(),
+  config: z.object({ Labels: z.record(z.string()).optional() }).optional(),
+});
+export type OciImageConfig = z.infer<typeof OciImageConfig>;
+
+/**
+ * OCI Image Manifest
+ * The same structure as docker image manifest, but mediaType is not required and is not present in the wild.
+ * https://github.com/opencontainers/image-spec/blob/main/manifest.md
+ */
+export const OciImageManifest = ManifestObject.extend({
+  mediaType: z
+    .literal('application/vnd.oci.image.manifest.v1+json')
+    .default('application/vnd.oci.image.manifest.v1+json'),
+  config: Descriptor.extend({
+    mediaType: z.enum([
+      'application/vnd.oci.image.config.v1+json',
+      'application/vnd.cncf.helm.config.v1+json',
+    ]),
+  }),
+  annotations: z.record(z.string()).optional(),
+});
+
+/**
+ * OCI Image List
+ * mediaType is not required.
+ * https://github.com/opencontainers/image-spec/blob/main/image-index.md
+ */
+export const OciImageIndexManifest = ManifestObject.extend({
+  mediaType: z
+    .literal('application/vnd.oci.image.index.v1+json')
+    .default('application/vnd.oci.image.index.v1+json'),
+  manifests: z.array(
+    Descriptor.extend({
+      mediaType: z.enum([
+        'application/vnd.oci.image.manifest.v1+json',
+        'application/vnd.oci.image.index.v1+json',
+      ]),
+      platform: OciPlatform,
+    })
+  ),
+  annotations: z.record(z.string()).optional(),
+});
+
+// Old Docker manifests
+
+/**
+ * Image Manifest
+ * https://docs.docker.com/registry/spec/manifest-v2-2/#image-manifest
+ */
+export const DistributionManifest = ManifestObject.extend({
+  mediaType: z.literal('application/vnd.docker.distribution.manifest.v2+json'),
+  config: Descriptor.extend({
+    mediaType: z.literal('application/vnd.docker.container.image.v1+json'),
+  }),
+});
+
+/**
+ * Manifest List
+ * https://docs.docker.com/registry/spec/manifest-v2-2/#manifest-list
+ */
+export const DistributionListManifest = ManifestObject.extend({
+  mediaType: z.literal(
+    'application/vnd.docker.distribution.manifest.list.v2+json'
+  ),
+  manifests: z.array(
+    Descriptor.extend({
+      mediaType: z.literal(
+        'application/vnd.docker.distribution.manifest.v2+json'
+      ),
+      platform: OciPlatform,
+    })
+  ),
+});
+
+// Combined manifests
+
+export const Manifest = z.union([
+  DistributionManifest,
+  DistributionListManifest,
+  OciImageManifest,
+  OciImageIndexManifest,
+]);
+
+export type Manifest = z.infer<typeof Manifest>;
diff --git a/lib/modules/datasource/docker/types.ts b/lib/modules/datasource/docker/types.ts
index 3b5a6440140b642135d55542a42ee9d06a4f55a6..9f72be577a051dba4bf5de3867c321f3a4bfad27 100644
--- a/lib/modules/datasource/docker/types.ts
+++ b/lib/modules/datasource/docker/types.ts
@@ -1,114 +1,4 @@
-/**
- * Media Types
- * https://docs.docker.com/registry/spec/manifest-v2-2/#media-types
- * https://github.com/opencontainers/image-spec/blob/main/media-types.md
- */
-export type MediaType =
-  | 'application/vnd.docker.distribution.manifest.v1+json' // manifestV1
-  | 'application/vnd.docker.distribution.manifest.v2+json' // manifestV2
-  | 'application/vnd.docker.distribution.manifest.list.v2+json' // manifestListV2
-  | 'application/vnd.oci.image.manifest.v1+json' // ociManifestV1
-  | 'application/vnd.oci.image.index.v1+json'; // ociManifestIndexV1
-
-export interface MediaObject {
-  readonly digest: string;
-  readonly mediaType: MediaType;
-  readonly size: number;
-}
-
-export interface ImageListImage extends MediaObject {
-  readonly mediaType:
-    | 'application/vnd.docker.distribution.manifest.v1+json'
-    | 'application/vnd.docker.distribution.manifest.v2+json';
-
-  readonly platform: OciPlatform;
-}
-
-/**
- * Manifest List
- * https://docs.docker.com/registry/spec/manifest-v2-2/#manifest-list-field-descriptions
- */
-export interface ImageList {
-  readonly schemaVersion: 2;
-  readonly mediaType: 'application/vnd.docker.distribution.manifest.list.v2+json';
-  readonly manifests: ImageListImage[];
-}
-
-/**
- * Image Manifest
- * https://docs.docker.com/registry/spec/manifest-v2-2/#image-manifest
- */
-export interface Image extends MediaObject {
-  readonly schemaVersion: 2;
-  readonly mediaType: 'application/vnd.docker.distribution.manifest.v2+json';
-  readonly config: MediaObject;
-}
-
-/**
- * OCI platform properties
- * https://github.com/opencontainers/image-spec/blob/main/image-index.md
- */
-export interface OciPlatform {
-  architecture?: string;
-  features?: string[];
-  os?: string;
-  'os.features'?: string[];
-  'os.version'?: string;
-  variant?: string;
-}
-
-/**
- * OCI content descriptor
- * https://github.com/opencontainers/image-spec/blob/main/descriptor.md
- */
-export interface OciDescriptor {
-  readonly mediaType?: MediaType;
-  readonly digest: string;
-  readonly size: number;
-  readonly urls: string[];
-  readonly annotations: Record<string, string>;
-}
-
-/**
- * OCI Image Manifest
- * The same structure as docker image manifest, but mediaType is not required and is not present in the wild.
- * https://github.com/opencontainers/image-spec/blob/main/manifest.md
- */
-export interface OciImage {
-  readonly schemaVersion: 2;
-  readonly mediaType?: 'application/vnd.oci.image.manifest.v1+json';
-  readonly config: OciDescriptor;
-  readonly layers: OciDescriptor[];
-  readonly annotations: Record<string, string>;
-}
-
-export interface OciImageListManifest extends OciDescriptor {
-  readonly mediaType?:
-    | 'application/vnd.oci.image.manifest.v1+json'
-    | 'application/vnd.oci.image.index.v1+json';
-  readonly platform: OciPlatform;
-}
-
-/**
- * OCI Image List
- * mediaType is not required.
- * https://github.com/opencontainers/image-spec/blob/main/image-index.md
- */
-export interface OciImageList {
-  readonly schemaVersion: 2;
-  readonly mediaType?: 'application/vnd.oci.image.index.v1+json';
-  readonly manifests: OciImageListManifest[];
-}
-
 export interface RegistryRepository {
   registryHost: string;
   dockerRepository: string;
 }
-
-/**
- * OCI Image Configuration
- * https://github.com/opencontainers/image-spec/blob/main/config.md
- */
-export interface ImageConfig {
-  readonly architecture: string;
-}