diff --git a/lib/modules/manager/argocd/extract.ts b/lib/modules/manager/argocd/extract.ts
index 26fdc84cec4eb049f04927e858c5a697535d3339..49d3daa6c9134fa48ec2f3b24df70127a4b4f6f8 100644
--- a/lib/modules/manager/argocd/extract.ts
+++ b/lib/modules/manager/argocd/extract.ts
@@ -11,11 +11,11 @@ import type {
   PackageDependency,
   PackageFileContent,
 } from '../types';
-import type {
+import {
   ApplicationDefinition,
-  ApplicationSource,
-  ApplicationSpec,
-} from './types';
+  type ApplicationSource,
+  type ApplicationSpec,
+} from './schema';
 import { fileTestRegex } from './util';
 
 export function extractPackageFile(
@@ -33,27 +33,21 @@ export function extractPackageFile(
 
   let definitions: ApplicationDefinition[];
   try {
-    // TODO: use schema (#9610)
-    definitions = parseYaml(content);
+    definitions = parseYaml(content, null, {
+      customSchema: ApplicationDefinition,
+      failureBehaviour: 'filter',
+    });
   } catch (err) {
     logger.debug({ err, packageFile }, 'Failed to parse ArgoCD definition.');
     return null;
   }
 
-  const deps = definitions.filter(is.plainObject).flatMap(processAppSpec);
+  const deps = definitions.flatMap(processAppSpec);
 
   return deps.length ? { deps } : null;
 }
 
 function processSource(source: ApplicationSource): PackageDependency | null {
-  if (
-    !source ||
-    !is.nonEmptyString(source.repoURL) ||
-    !is.nonEmptyString(source.targetRevision)
-  ) {
-    return null;
-  }
-
   // a chart variable is defined this is helm declaration
   if (source.chart) {
     // assume OCI helm chart if repoURL doesn't contain explicit protocol
@@ -89,14 +83,10 @@ function processSource(source: ApplicationSource): PackageDependency | null {
 function processAppSpec(
   definition: ApplicationDefinition,
 ): PackageDependency[] {
-  const spec: ApplicationSpec | null | undefined =
+  const spec: ApplicationSpec =
     definition.kind === 'Application'
-      ? definition?.spec
-      : definition?.spec?.template?.spec;
-
-  if (is.nullOrUndefined(spec)) {
-    return [];
-  }
+      ? definition.spec
+      : definition.spec.template.spec;
 
   const deps: (PackageDependency | null)[] = [];
 
diff --git a/lib/modules/manager/argocd/schema.ts b/lib/modules/manager/argocd/schema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b5de403e97b1d6fc5bc7c09526acd4558bc8d2c3
--- /dev/null
+++ b/lib/modules/manager/argocd/schema.ts
@@ -0,0 +1,35 @@
+import { z } from 'zod';
+
+export const KubernetesResource = z.object({
+  apiVersion: z.string(),
+});
+
+export const ApplicationSource = z.object({
+  chart: z.string().optional(),
+  repoURL: z.string(),
+  targetRevision: z.string(),
+});
+export type ApplicationSource = z.infer<typeof ApplicationSource>;
+
+export const ApplicationSpec = z.object({
+  source: ApplicationSource.optional(),
+  sources: z.array(ApplicationSource).optional(),
+});
+export type ApplicationSpec = z.infer<typeof ApplicationSpec>;
+
+export const Application = KubernetesResource.extend({
+  kind: z.literal('Application'),
+  spec: ApplicationSpec,
+});
+
+export const ApplicationSet = KubernetesResource.extend({
+  kind: z.literal('ApplicationSet'),
+  spec: z.object({
+    template: z.object({
+      spec: ApplicationSpec,
+    }),
+  }),
+});
+
+export const ApplicationDefinition = Application.or(ApplicationSet);
+export type ApplicationDefinition = z.infer<typeof ApplicationDefinition>;
diff --git a/lib/modules/manager/argocd/types.ts b/lib/modules/manager/argocd/types.ts
deleted file mode 100644
index f8b473e6d195afaf88e7676f3e1af16e2473edad..0000000000000000000000000000000000000000
--- a/lib/modules/manager/argocd/types.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-export interface KubernetesResource {
-  apiVersion: string;
-}
-
-export interface ApplicationSource {
-  chart?: string;
-  repoURL: string;
-  targetRevision: string;
-}
-
-export interface ApplicationSpec {
-  source?: ApplicationSource;
-  sources?: ApplicationSource[];
-}
-
-export interface Application extends KubernetesResource {
-  kind: 'Application';
-  spec: ApplicationSpec;
-}
-
-export interface ApplicationSet extends KubernetesResource {
-  kind: 'ApplicationSet';
-  spec: {
-    template: {
-      spec: ApplicationSpec;
-    };
-  };
-}
-
-export type ApplicationDefinition = Application | ApplicationSet;
diff --git a/lib/modules/manager/crossplane/extract.ts b/lib/modules/manager/crossplane/extract.ts
index 993dbdadf961d01ee2020d04674e40ba5d7ec5fe..c247351ee5d7c486dc37d8c11c9c223a544e6428 100644
--- a/lib/modules/manager/crossplane/extract.ts
+++ b/lib/modules/manager/crossplane/extract.ts
@@ -6,7 +6,7 @@ import type {
   PackageDependency,
   PackageFileContent,
 } from '../types';
-import { XPKGSchema } from './schema';
+import { type XPKG, XPKGSchema } from './schema';
 
 export function extractPackageFile(
   content: string,
@@ -19,9 +19,12 @@ export function extractPackageFile(
     return null;
   }
 
-  let list = [];
+  let list: XPKG[] = [];
   try {
-    list = parseYaml(content);
+    list = parseYaml(content, null, {
+      customSchema: XPKGSchema,
+      failureBehaviour: 'filter',
+    });
   } catch (err) {
     logger.debug(
       { err, packageFile },
@@ -31,16 +34,7 @@ export function extractPackageFile(
   }
 
   const deps: PackageDependency[] = [];
-  for (const item of list) {
-    const parsed = XPKGSchema.safeParse(item);
-    if (!parsed.success) {
-      logger.trace(
-        { item, errors: parsed.error },
-        'Invalid Crossplane package',
-      );
-      continue;
-    }
-    const xpkg = parsed.data;
+  for (const xpkg of list) {
     const dep = getDep(xpkg.spec.package, true, extractConfig?.registryAliases);
     dep.depType = xpkg.kind.toLowerCase();
     deps.push(dep);
diff --git a/lib/modules/manager/docker-compose/extract.ts b/lib/modules/manager/docker-compose/extract.ts
index 6b254ae20f6c224f05933550d7b9720cceb138a5..255d47ae7d35a914b4224c94690de3ae4757c1ee 100644
--- a/lib/modules/manager/docker-compose/extract.ts
+++ b/lib/modules/manager/docker-compose/extract.ts
@@ -4,7 +4,7 @@ import { newlineRegex, regEx } from '../../../util/regex';
 import { parseSingleYaml } from '../../../util/yaml';
 import { getDep } from '../dockerfile/extract';
 import type { ExtractConfig, PackageFileContent } from '../types';
-import type { DockerComposeConfig } from './types';
+import { DockerComposeFile } from './schema';
 
 class LineMapper {
   private imageLines: { line: string; lineNumber: number; used: boolean }[];
@@ -34,24 +34,12 @@ export function extractPackageFile(
   extractConfig: ExtractConfig,
 ): PackageFileContent | null {
   logger.debug(`docker-compose.extractPackageFile(${packageFile})`);
-  let config: DockerComposeConfig;
+  let config: DockerComposeFile;
   try {
-    // TODO: use schema (#9610)
-    config = parseSingleYaml(content, { json: true });
-    if (!config) {
-      logger.debug(
-        { packageFile },
-        'Null config when parsing Docker Compose content',
-      );
-      return null;
-    }
-    if (typeof config !== 'object') {
-      logger.debug(
-        { packageFile, type: typeof config },
-        'Unexpected type for Docker Compose content',
-      );
-      return null;
-    }
+    config = parseSingleYaml(content, {
+      json: true,
+      customSchema: DockerComposeFile,
+    });
   } catch (err) {
     logger.debug(
       { err, packageFile },
diff --git a/lib/modules/manager/docker-compose/schema.ts b/lib/modules/manager/docker-compose/schema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6f521e93389e0ad5270888900f03676a3cc68350
--- /dev/null
+++ b/lib/modules/manager/docker-compose/schema.ts
@@ -0,0 +1,23 @@
+import { z } from 'zod';
+
+const DockerComposeService = z.object({
+  image: z.string().optional(),
+  build: z
+    .object({
+      context: z.string().optional(),
+      dockerfile: z.string().optional(),
+    })
+    .optional(),
+});
+
+const DockerComposeFileV1 = z.record(DockerComposeService);
+const DockerComposeFileModern = z.object({
+  // compose does not use this strictly, so we shouldn't be either
+  // https://docs.docker.com/compose/compose-file/04-version-and-name/#version-top-level-element
+  version: z.string().optional(),
+  services: z.record(DockerComposeService),
+});
+
+export const DockerComposeFile =
+  DockerComposeFileModern.or(DockerComposeFileV1);
+export type DockerComposeFile = z.infer<typeof DockerComposeFile>;
diff --git a/lib/modules/manager/docker-compose/types.ts b/lib/modules/manager/docker-compose/types.ts
deleted file mode 100644
index 25495d806264d48412a30884fff8c2a04a48a83e..0000000000000000000000000000000000000000
--- a/lib/modules/manager/docker-compose/types.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export type DockerComposeConfig = {
-  version?: string;
-  services?: Record<string, DockerComposeService>;
-} & Record<string, DockerComposeService>;
-
-export interface DockerComposeService {
-  image?: string;
-  build?: {
-    context?: string;
-    dockerfile?: string;
-  };
-}
diff --git a/lib/modules/manager/fleet/extract.ts b/lib/modules/manager/fleet/extract.ts
index 6ea8a243476158957286d2d0852775e3e3a92bc0..32c4c705c336bbaf9387997eb0c29c91856a6591 100644
--- a/lib/modules/manager/fleet/extract.ts
+++ b/lib/modules/manager/fleet/extract.ts
@@ -6,7 +6,7 @@ import { GitTagsDatasource } from '../../datasource/git-tags';
 import { HelmDatasource } from '../../datasource/helm';
 import { checkIfStringIsPath } from '../terraform/util';
 import type { PackageDependency, PackageFileContent } from '../types';
-import type { FleetFile, FleetHelmBlock, GitRepo } from './types';
+import { FleetFile, type FleetHelmBlock, GitRepo } from './schema';
 
 function extractGitRepo(doc: GitRepo): PackageDependency {
   const dep: PackageDependency = {
@@ -119,23 +119,21 @@ export function extractPackageFile(
 
   try {
     if (regEx('fleet.ya?ml').test(packageFile)) {
-      // TODO: use schema (#9610)
-      const docs = parseYaml<FleetFile>(content, null, {
+      const docs = parseYaml(content, null, {
         json: true,
+        customSchema: FleetFile,
+        failureBehaviour: 'filter',
       });
-      const fleetDeps = docs
-        .filter((doc) => is.truthy(doc?.helm))
-        .flatMap((doc) => extractFleetFile(doc));
+      const fleetDeps = docs.flatMap(extractFleetFile);
 
       deps.push(...fleetDeps);
     } else {
-      // TODO: use schema (#9610)
-      const docs = parseYaml<GitRepo>(content, null, {
+      const docs = parseYaml(content, null, {
         json: true,
+        customSchema: GitRepo,
+        failureBehaviour: 'filter',
       });
-      const gitRepoDeps = docs
-        .filter((doc) => doc.kind === 'GitRepo') // ensure only GitRepo manifests are processed
-        .flatMap((doc) => extractGitRepo(doc));
+      const gitRepoDeps = docs.flatMap(extractGitRepo);
       deps.push(...gitRepoDeps);
     }
   } catch (err) {
diff --git a/lib/modules/manager/fleet/schema.ts b/lib/modules/manager/fleet/schema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..08d3d4342a450be31fcad04df4343309bf2012c1
--- /dev/null
+++ b/lib/modules/manager/fleet/schema.ts
@@ -0,0 +1,45 @@
+import { z } from 'zod';
+
+const FleetHelmBlock = z.object({
+  chart: z.string().optional(),
+  repo: z.string().optional(),
+  version: z.string().optional(),
+});
+export type FleetHelmBlock = z.infer<typeof FleetHelmBlock>;
+
+const FleetFileHelm = FleetHelmBlock.extend({
+  releaseName: z.string(),
+});
+
+/**
+  Represent a GitRepo Kubernetes manifest of Fleet.
+  @link https://fleet.rancher.io/gitrepo-add/#create-gitrepo-instance
+ */
+export const GitRepo = z.object({
+  metadata: z.object({
+    name: z.string(),
+  }),
+  kind: z.string(),
+  spec: z.object({
+    repo: z.string().optional(),
+    revision: z.string().optional(),
+  }),
+});
+export type GitRepo = z.infer<typeof GitRepo>;
+
+/**
+ Represent a Bundle configuration of Fleet, which is located in `fleet.yaml` files.
+ @link https://fleet.rancher.io/gitrepo-structure/#fleetyaml
+ */
+export const FleetFile = z.object({
+  helm: FleetFileHelm,
+  targetCustomizations: z
+    .array(
+      z.object({
+        name: z.string(),
+        helm: FleetHelmBlock.partial().optional(),
+      }),
+    )
+    .optional(),
+});
+export type FleetFile = z.infer<typeof FleetFile>;
diff --git a/lib/modules/manager/fleet/types.ts b/lib/modules/manager/fleet/types.ts
deleted file mode 100644
index c3e5b58743a8ca6a7d2be6bae43ecfea1adc0d65..0000000000000000000000000000000000000000
--- a/lib/modules/manager/fleet/types.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
-  Represent a GitRepo Kubernetes manifest of Fleet.
-  @link https://fleet.rancher.io/gitrepo-add/#create-gitrepo-instance
- */
-export interface GitRepo {
-  metadata: {
-    name: string;
-  };
-  kind: string;
-  spec: {
-    repo: string;
-    revision?: string;
-  };
-}
-
-/**
- Represent a Bundle configuration of Fleet, which is located in `fleet.yaml` files.
- @link https://fleet.rancher.io/gitrepo-structure/#fleetyaml
- */
-export interface FleetFile {
-  helm: FleetFileHelm;
-  targetCustomizations?: {
-    name: string;
-    helm: FleetHelmBlock;
-  }[];
-}
-
-export interface FleetHelmBlock {
-  chart: string;
-  repo?: string;
-  version?: string;
-}
-
-export interface FleetFileHelm extends FleetHelmBlock {
-  releaseName: string;
-}
diff --git a/lib/modules/manager/flux/extract.spec.ts b/lib/modules/manager/flux/extract.spec.ts
index 958ab562466ad7afb1a0c1c5c31473f1a6a4c718..6c4b5c4f1192ddc040c1a3874673a040a6a01b59 100644
--- a/lib/modules/manager/flux/extract.spec.ts
+++ b/lib/modules/manager/flux/extract.spec.ts
@@ -194,16 +194,7 @@ describe('modules/manager/flux/extract', () => {
         `,
         'test.yaml',
       );
-      expect(result).toEqual({
-        deps: [
-          {
-            currentValue: '2.0.2',
-            datasource: HelmDatasource.id,
-            depName: 'sealed-secrets',
-            skipReason: 'unknown-registry',
-          },
-        ],
-      });
+      expect(result).toBeNull();
     });
 
     it('does not match HelmRelease resources without a sourceRef', () => {
@@ -215,6 +206,7 @@ describe('modules/manager/flux/extract', () => {
           kind: HelmRelease
           metadata:
             name: sealed-secrets
+            namespace: test
           spec:
             chart:
               spec:
@@ -253,16 +245,7 @@ describe('modules/manager/flux/extract', () => {
         `,
         'test.yaml',
       );
-      expect(result).toEqual({
-        deps: [
-          {
-            currentValue: '2.0.2',
-            datasource: HelmDatasource.id,
-            depName: 'sealed-secrets',
-            skipReason: 'unknown-registry',
-          },
-        ],
-      });
+      expect(result).toBeNull();
     });
 
     it('ignores HelmRepository resources without a namespace', () => {
diff --git a/lib/modules/manager/flux/extract.ts b/lib/modules/manager/flux/extract.ts
index 2b03f05eac54c8da5b06a28918ac038c900f872e..7e4fd03ec58d0051ee4ce051d855730f92d19824 100644
--- a/lib/modules/manager/flux/extract.ts
+++ b/lib/modules/manager/flux/extract.ts
@@ -19,11 +19,10 @@ import type {
   PackageFileContent,
 } from '../types';
 import { isSystemManifest, systemManifestHeaderRegex } from './common';
+import { FluxResource, type HelmRepository } from './schema';
 import type {
   FluxManagerData,
   FluxManifest,
-  FluxResource,
-  HelmRepository,
   ResourceFluxManifest,
   SystemFluxManifest,
 } from './types';
@@ -45,61 +44,21 @@ function readManifest(
     };
   }
 
-  const manifest: FluxManifest = {
-    kind: 'resource',
-    file: packageFile,
-    resources: [],
-  };
-  let resources: FluxResource[];
   try {
-    // TODO: use schema (#9610)
-    resources = parseYaml(content, null, { json: true });
+    const manifest: FluxManifest = {
+      kind: 'resource',
+      file: packageFile,
+      resources: parseYaml(content, null, {
+        json: true,
+        customSchema: FluxResource,
+        failureBehaviour: 'filter',
+      }),
+    };
+    return manifest;
   } catch (err) {
     logger.debug({ err, packageFile }, 'Failed to parse Flux manifest');
     return null;
   }
-
-  // It's possible there are other non-Flux HelmRelease/HelmRepository CRs out there, so we filter based on apiVersion.
-  for (const resource of resources) {
-    switch (resource?.kind) {
-      case 'HelmRelease':
-        if (
-          resource.apiVersion?.startsWith('helm.toolkit.fluxcd.io/') &&
-          resource.spec?.chart?.spec?.chart
-        ) {
-          manifest.resources.push(resource);
-        }
-        break;
-      case 'HelmRepository':
-        if (
-          resource.apiVersion?.startsWith('source.toolkit.fluxcd.io/') &&
-          resource.metadata?.name &&
-          resource.metadata.namespace &&
-          resource.spec?.url
-        ) {
-          manifest.resources.push(resource);
-        }
-        break;
-      case 'GitRepository':
-        if (
-          resource.apiVersion?.startsWith('source.toolkit.fluxcd.io/') &&
-          resource.spec?.url
-        ) {
-          manifest.resources.push(resource);
-        }
-        break;
-      case 'OCIRepository':
-        if (
-          resource.apiVersion?.startsWith('source.toolkit.fluxcd.io/') &&
-          resource.spec?.url
-        ) {
-          manifest.resources.push(resource);
-        }
-        break;
-    }
-  }
-
-  return manifest;
 }
 
 const githubUrlRegex = regEx(
diff --git a/lib/modules/manager/flux/schema.ts b/lib/modules/manager/flux/schema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5030ab16ef5aed49320742cac228c2857ef7cebf
--- /dev/null
+++ b/lib/modules/manager/flux/schema.ts
@@ -0,0 +1,76 @@
+import { z } from 'zod';
+
+export const KubernetesResource = z.object({
+  apiVersion: z.string(),
+  kind: z.string(),
+  metadata: z.object({
+    name: z.string(),
+    // For Flux, the namespace property is optional, but matching HelmReleases to HelmRepositories would be
+    // much more difficult without it (we'd have to examine the parent Kustomizations to discover the value),
+    // so we require it for renovation.
+    namespace: z.string().optional(),
+  }),
+});
+
+export const HelmRelease = KubernetesResource.extend({
+  apiVersion: z.string().startsWith('helm.toolkit.fluxcd.io/'),
+  kind: z.literal('HelmRelease'),
+  spec: z.object({
+    chart: z.object({
+      spec: z.object({
+        chart: z.string(),
+        version: z.string().optional(),
+        sourceRef: z
+          .object({
+            kind: z.string().optional(),
+            name: z.string().optional(),
+            namespace: z.string().optional(),
+          })
+          .optional(),
+      }),
+    }),
+  }),
+});
+
+export const HelmRepository = KubernetesResource.extend({
+  apiVersion: z.string().startsWith('source.toolkit.fluxcd.io/'),
+  kind: z.literal('HelmRepository'),
+  spec: z.object({
+    url: z.string(),
+    type: z.enum(['oci', 'default']).optional(),
+  }),
+});
+export type HelmRepository = z.infer<typeof HelmRepository>;
+
+export const GitRepository = KubernetesResource.extend({
+  apiVersion: z.string().startsWith('source.toolkit.fluxcd.io/'),
+  kind: z.literal('GitRepository'),
+  spec: z.object({
+    url: z.string(),
+    ref: z
+      .object({
+        tag: z.string().optional(),
+        commit: z.string().optional(),
+      })
+      .optional(),
+  }),
+});
+
+export const OCIRepository = KubernetesResource.extend({
+  apiVersion: z.string().startsWith('source.toolkit.fluxcd.io/'),
+  kind: z.literal('OCIRepository'),
+  spec: z.object({
+    url: z.string(),
+    ref: z
+      .object({
+        tag: z.string().optional(),
+        digest: z.string().optional(),
+      })
+      .optional(),
+  }),
+});
+
+export const FluxResource = HelmRelease.or(HelmRepository)
+  .or(GitRepository)
+  .or(OCIRepository);
+export type FluxResource = z.infer<typeof FluxResource>;
diff --git a/lib/modules/manager/flux/types.ts b/lib/modules/manager/flux/types.ts
index 9895abc28b29e6f81a1bf51695df775254afde8c..20e80e788180dd1edbd1c394c89739f1a489be26 100644
--- a/lib/modules/manager/flux/types.ts
+++ b/lib/modules/manager/flux/types.ts
@@ -1,73 +1,9 @@
+import type { FluxResource } from './schema';
+
 export type FluxManagerData = {
   components: string;
 };
 
-export interface KubernetesResource {
-  apiVersion: string;
-  metadata: {
-    name: string;
-    // For Flux, the namespace property is optional, but matching HelmReleases to HelmRepositories would be
-    // much more difficult without it (we'd have to examine the parent Kustomizations to discover the value),
-    // so we require it for renovation.
-    namespace: string;
-  };
-}
-
-export interface HelmRelease extends KubernetesResource {
-  kind: 'HelmRelease';
-  spec: {
-    chart: {
-      spec: {
-        chart: string;
-        sourceRef: {
-          kind: string;
-          name: string;
-          namespace?: string;
-        };
-        version?: string;
-      };
-    };
-  };
-}
-
-export type HelmRepositoryType = 'oci' | 'default';
-
-export interface HelmRepository extends KubernetesResource {
-  kind: 'HelmRepository';
-  spec: {
-    url: string;
-    type: HelmRepositoryType;
-  };
-}
-
-export interface GitRepository extends KubernetesResource {
-  kind: 'GitRepository';
-  spec: {
-    ref: {
-      tag?: string;
-      commit?: string;
-    };
-    url: string;
-  };
-}
-
-export interface OciRepository extends KubernetesResource {
-  kind: 'OCIRepository';
-  spec: {
-    ref: {
-      digest?: string;
-      tag?: string;
-    };
-    url: string;
-  };
-}
-
-export type FluxResource =
-  | HelmRelease
-  | HelmRepository
-  | GitRepository
-  | OciRepository;
-
 export interface FluxFile {
   file: string;
 }