diff --git a/lib/modules/manager/circleci/__fixtures__/config2.yml b/lib/modules/manager/circleci/__fixtures__/config2.yml
index 3f885f3e5c0a1596085b88a729b35ea2d15973ba..df2a528d8a22288ef151dbef0148dfd7902e5649 100644
--- a/lib/modules/manager/circleci/__fixtures__/config2.yml
+++ b/lib/modules/manager/circleci/__fixtures__/config2.yml
@@ -9,7 +9,7 @@ orbs:
   no-version: abc/def
 
   # Comments help me understand my work.
-  volatile: zzz/zzz@volatile # Comments help me understand my work.
+  volatile: "zzz/zzz@volatile" # Comments help me understand my work.
 
 test_plan: &test_plan
   steps:
diff --git a/lib/modules/manager/circleci/__fixtures__/config3.yml b/lib/modules/manager/circleci/__fixtures__/config3.yml
index 307ade4d7fcf144177b0e425c8e21c9e6c036ec6..5523b2c39ae3aefaf09b04534d96ebb8486afa0b 100644
--- a/lib/modules/manager/circleci/__fixtures__/config3.yml
+++ b/lib/modules/manager/circleci/__fixtures__/config3.yml
@@ -1,18 +1,16 @@
-aliases:  
+aliases:
   - &nodejs
     image: cimg/node:14.8.0
 
 version: 2
 jobs:
-  checkout: 
-    <<: *defaults
+  checkout:
     docker:
       - *nodejs
     steps:
       - run: yarn build:runtime
 
   release_docker:
-    <<: *defaults
     machine:
       image: ubuntu-1604:201903-01
       docker_layer_caching: true
diff --git a/lib/modules/manager/circleci/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/circleci/__snapshots__/extract.spec.ts.snap
index 77e4c1db6d89f86dbaf3f2e6d3da0ff0ca3f573d..99741c70d4979161d09d32456cc8b79361eb3b1c 100644
--- a/lib/modules/manager/circleci/__snapshots__/extract.spec.ts.snap
+++ b/lib/modules/manager/circleci/__snapshots__/extract.spec.ts.snap
@@ -10,7 +10,6 @@ exports[`modules/manager/circleci/extract extractPackageFile() extracts multiple
     "depName": "node",
     "depType": "docker",
     "replaceString": "node",
-    "versioning": "docker",
   },
   {
     "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}",
@@ -20,7 +19,6 @@ exports[`modules/manager/circleci/extract extractPackageFile() extracts multiple
     "depName": "node",
     "depType": "docker",
     "replaceString": "node:4",
-    "versioning": "docker",
   },
   {
     "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}",
@@ -30,7 +28,6 @@ exports[`modules/manager/circleci/extract extractPackageFile() extracts multiple
     "depName": "node",
     "depType": "docker",
     "replaceString": "node:6",
-    "versioning": "docker",
   },
   {
     "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}",
@@ -40,7 +37,6 @@ exports[`modules/manager/circleci/extract extractPackageFile() extracts multiple
     "depName": "node",
     "depType": "docker",
     "replaceString": "node:8.9.0",
-    "versioning": "docker",
   },
 ]
 `;
diff --git a/lib/modules/manager/circleci/extract.spec.ts b/lib/modules/manager/circleci/extract.spec.ts
index 4222a22a5423ec54d339e96195ae18dd3c099fde..827f791248470e1909bddd8af0bc9a7118dcbaa9 100644
--- a/lib/modules/manager/circleci/extract.spec.ts
+++ b/lib/modules/manager/circleci/extract.spec.ts
@@ -51,6 +51,7 @@ describe('modules/manager/circleci/extract', () => {
       const res = extractPackageFile(file3);
       expect(res?.deps).toMatchObject([
         { currentValue: '14.8.0', depName: 'cimg/node' },
+        { currentValue: '14.8.0', depName: 'cimg/node' },
       ]);
     });
 
diff --git a/lib/modules/manager/circleci/extract.ts b/lib/modules/manager/circleci/extract.ts
index 9d971de87724c03e0045ae6f12b57e640c433721..4de5c6b58e0ea1bf96536af6204e114e73692ba9 100644
--- a/lib/modules/manager/circleci/extract.ts
+++ b/lib/modules/manager/circleci/extract.ts
@@ -1,9 +1,11 @@
 import { logger } from '../../../logger';
-import { newlineRegex, regEx } from '../../../util/regex';
+import { coerceArray } from '../../../util/array';
+import { parseSingleYaml } from '../../../util/yaml';
 import { OrbDatasource } from '../../datasource/orb';
 import * as npmVersioning from '../../versioning/npm';
 import { getDep } from '../dockerfile/extract';
 import type { PackageDependency, PackageFileContent } from '../types';
+import { CircleCiFile } from './schema';
 
 export function extractPackageFile(
   content: string,
@@ -11,70 +13,38 @@ export function extractPackageFile(
 ): PackageFileContent | null {
   const deps: PackageDependency[] = [];
   try {
-    const lines = content.split(newlineRegex);
-    for (let lineNumber = 0; lineNumber < lines.length; lineNumber += 1) {
-      const line = lines[lineNumber];
-      const orbs = regEx(/^\s*orbs:\s*$/).exec(line);
-      if (orbs) {
-        logger.trace(`Matched orbs on line ${lineNumber}`);
-        let foundOrbOrNoop: boolean;
-        do {
-          foundOrbOrNoop = false;
-          const orbLine = lines[lineNumber + 1];
-          logger.trace(`orbLine: "${orbLine}"`);
-          const yamlNoop = regEx(/^\s*(#|$)/).exec(orbLine);
-          if (yamlNoop) {
-            logger.debug('orbNoop');
-            foundOrbOrNoop = true;
-            lineNumber += 1;
-            continue;
-          }
-          const orbMatch = regEx(/^\s+([^:]+):\s(.+?)(?:\s*#.*)?$/).exec(
-            orbLine,
-          );
-          if (orbMatch) {
-            logger.trace('orbMatch');
-            foundOrbOrNoop = true;
-            lineNumber += 1;
-            const depName = orbMatch[1];
-            const [orbName, currentValue] = orbMatch[2].split('@');
-            const dep: PackageDependency = {
-              depType: 'orb',
-              depName,
-              currentValue,
-              datasource: OrbDatasource.id,
-              packageName: orbName,
-              commitMessageTopic: '{{{depName}}} orb',
-              versioning: npmVersioning.id,
-            };
-            deps.push(dep);
-          }
-        } while (foundOrbOrNoop);
-      }
-      const match = regEx(/^\s*-? image:\s*'?"?([^\s'"]+)'?"?\s*$/).exec(line);
-      if (match) {
-        const currentFrom = match[1];
-        const dep = getDep(currentFrom);
-        logger.debug(
-          {
-            depName: dep.depName,
-            currentValue: dep.currentValue,
-            currentDigest: dep.currentDigest,
-          },
-          'CircleCI docker image',
-        );
-        dep.depType = 'docker';
-        dep.versioning = 'docker';
-        if (
-          !dep.depName?.startsWith('ubuntu-') &&
-          !dep.depName?.startsWith('windows-server-') &&
-          !dep.depName?.startsWith('android-') &&
-          dep.depName !== 'android'
-        ) {
-          deps.push(dep);
-        }
+    const parsed = parseSingleYaml(content, {
+      customSchema: CircleCiFile,
+    });
+
+    for (const [key, orb] of Object.entries(parsed.orbs ?? {})) {
+      const [packageName, currentValue] = orb.split('@');
+
+      deps.push({
+        depName: key,
+        packageName,
+        depType: 'orb',
+        currentValue,
+        versioning: npmVersioning.id,
+        datasource: OrbDatasource.id,
+      });
+    }
+
+    for (const job of Object.values(parsed.jobs)) {
+      for (const dockerElement of coerceArray(job.docker)) {
+        deps.push({
+          ...getDep(dockerElement.image),
+          depType: 'docker',
+        });
       }
     }
+
+    for (const alias of coerceArray(parsed.aliases)) {
+      deps.push({
+        ...getDep(alias.image),
+        depType: 'docker',
+      });
+    }
   } catch (err) /* istanbul ignore next */ {
     logger.debug({ err, packageFile }, 'Error extracting circleci images');
   }
diff --git a/lib/modules/manager/circleci/schema.ts b/lib/modules/manager/circleci/schema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..09c945bd117ee496ffbc62110720ae700ccef8b4
--- /dev/null
+++ b/lib/modules/manager/circleci/schema.ts
@@ -0,0 +1,15 @@
+import { z } from 'zod';
+
+export const CircleCiDocker = z.object({
+  image: z.string(),
+});
+
+export const CircleCiJob = z.object({
+  docker: z.array(CircleCiDocker).optional(),
+});
+
+export const CircleCiFile = z.object({
+  aliases: z.array(CircleCiDocker).optional(),
+  jobs: z.record(z.string(), CircleCiJob),
+  orbs: z.record(z.string()).optional(),
+});