diff --git a/lib/modules/datasource/api.ts b/lib/modules/datasource/api.ts
index 3048b2eb4e5a8b7958ce36f9563449f4bc812cf1..e75ad8aa10bf9cdb022b231efeeed214f3695951 100644
--- a/lib/modules/datasource/api.ts
+++ b/lib/modules/datasource/api.ts
@@ -3,6 +3,7 @@ import { AwsMachineImageDataSource } from './aws-machine-image';
 import { AwsRdsDataSource } from './aws-rds';
 import { AzureBicepResourceDatasource } from './azure-bicep-resource';
 import { AzurePipelinesTasksDatasource } from './azure-pipelines-tasks';
+import { BazelDatasource } from './bazel';
 import { BitbucketTagsDatasource } from './bitbucket-tags';
 import { CdnJsDatasource } from './cdnjs';
 import { ClojureDatasource } from './clojure';
@@ -62,6 +63,7 @@ api.set(AwsMachineImageDataSource.id, new AwsMachineImageDataSource());
 api.set(AwsRdsDataSource.id, new AwsRdsDataSource());
 api.set(AzureBicepResourceDatasource.id, new AzureBicepResourceDatasource());
 api.set(AzurePipelinesTasksDatasource.id, new AzurePipelinesTasksDatasource());
+api.set(BazelDatasource.id, new BazelDatasource());
 api.set(BitbucketTagsDatasource.id, new BitbucketTagsDatasource());
 api.set(CdnJsDatasource.id, new CdnJsDatasource());
 api.set(ClojureDatasource.id, new ClojureDatasource());
diff --git a/lib/modules/datasource/bazel/__fixtures__/metadata-no-yanked-versions.json b/lib/modules/datasource/bazel/__fixtures__/metadata-no-yanked-versions.json
new file mode 100644
index 0000000000000000000000000000000000000000..2bbf5b1186fc61bc784ebb3800ef76b017870fd8
--- /dev/null
+++ b/lib/modules/datasource/bazel/__fixtures__/metadata-no-yanked-versions.json
@@ -0,0 +1,9 @@
+{
+    "versions": [
+        "0.14.8",
+        "0.14.9",
+        "0.15.0",
+        "0.16.0"
+    ],
+    "yanked_versions": {}
+}
diff --git a/lib/modules/datasource/bazel/__fixtures__/metadata-with-yanked-versions.json b/lib/modules/datasource/bazel/__fixtures__/metadata-with-yanked-versions.json
new file mode 100644
index 0000000000000000000000000000000000000000..213792c075a86a6bb4e668cd419d65e6a6f9dc35
--- /dev/null
+++ b/lib/modules/datasource/bazel/__fixtures__/metadata-with-yanked-versions.json
@@ -0,0 +1,11 @@
+{
+    "versions": [
+        "0.14.8",
+        "0.14.9",
+        "0.15.0",
+        "0.16.0"
+    ],
+    "yanked_versions": {
+      "0.15.0": "Very bad bug."
+    }
+}
diff --git a/lib/modules/datasource/bazel/index.spec.ts b/lib/modules/datasource/bazel/index.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c9ba6ebb67437f826263bf6af8deb4b41ce27d05
--- /dev/null
+++ b/lib/modules/datasource/bazel/index.spec.ts
@@ -0,0 +1,82 @@
+import { getPkgReleases } from '..';
+import { Fixtures } from '../../../../test/fixtures';
+import * as httpMock from '../../../../test/http-mock';
+import { EXTERNAL_HOST_ERROR } from '../../../constants/error-messages';
+import { BazelDatasource } from '.';
+
+const datasource = BazelDatasource.id;
+const defaultRegistryUrl = BazelDatasource.bazelCentralRepoUrl;
+const packageName = 'rules_foo';
+const path = BazelDatasource.packageMetadataPath(packageName);
+
+describe('modules/datasource/bazel/index', () => {
+  describe('getReleases', () => {
+    it('throws for error', async () => {
+      httpMock.scope(defaultRegistryUrl).get(path).replyWithError('error');
+      await expect(getPkgReleases({ datasource, packageName })).rejects.toThrow(
+        EXTERNAL_HOST_ERROR
+      );
+    });
+
+    it('returns null for 404', async () => {
+      httpMock.scope(defaultRegistryUrl).get(path).reply(404);
+      expect(await getPkgReleases({ datasource, packageName })).toBeNull();
+    });
+
+    it('returns null for empty result', async () => {
+      httpMock.scope(defaultRegistryUrl).get(path).reply(200, {});
+      expect(await getPkgReleases({ datasource, packageName })).toBeNull();
+    });
+
+    it('returns null for empty 200 OK', async () => {
+      httpMock
+        .scope(defaultRegistryUrl)
+        .get(path)
+        .reply(200, '{ "versions": [], "yanked_versions": {} }');
+      expect(await getPkgReleases({ datasource, packageName })).toBeNull();
+    });
+
+    it('throws for 5xx', async () => {
+      httpMock.scope(defaultRegistryUrl).get(path).reply(502);
+      await expect(getPkgReleases({ datasource, packageName })).rejects.toThrow(
+        EXTERNAL_HOST_ERROR
+      );
+    });
+
+    it('metadata without yanked versions', async () => {
+      httpMock
+        .scope(defaultRegistryUrl)
+        .get(path)
+        .reply(200, Fixtures.get('metadata-no-yanked-versions.json'));
+      const res = await getPkgReleases({ datasource, packageName });
+      expect(res).toEqual({
+        registryUrl:
+          'https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main',
+        releases: [
+          { version: '0.14.8' },
+          { version: '0.14.9' },
+          { version: '0.15.0' },
+          { version: '0.16.0' },
+        ],
+      });
+    });
+
+    it('metadata with yanked versions', async () => {
+      httpMock
+        .scope(defaultRegistryUrl)
+        .get(path)
+        .reply(200, Fixtures.get('metadata-with-yanked-versions.json'));
+      const res = await getPkgReleases({ datasource, packageName });
+      expect(res).toEqual({
+        registryUrl:
+          'https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main',
+        releases: [
+          { version: '0.14.8' },
+          { version: '0.14.9' },
+          { version: '0.15.0', isDeprecated: true },
+          { version: '0.16.0' },
+        ],
+      });
+    });
+  });
+});
diff --git a/lib/modules/datasource/bazel/index.ts b/lib/modules/datasource/bazel/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2a15a0eb84f8305fa6ec4b51ffdd2c8577e6a0b5
--- /dev/null
+++ b/lib/modules/datasource/bazel/index.ts
@@ -0,0 +1,70 @@
+import is from '@sindresorhus/is';
+import { ExternalHostError } from '../../../types/errors/external-host-error';
+import { cache } from '../../../util/cache/package/decorator';
+import { HttpError } from '../../../util/http';
+import { joinUrlParts } from '../../../util/url';
+import { BzlmodVersion } from '../../versioning/bazel-module/bzlmod-version';
+import { Datasource } from '../datasource';
+import type { GetReleasesConfig, Release, ReleaseResult } from '../types';
+import { BazelModuleMetadata } from './schema';
+
+export class BazelDatasource extends Datasource {
+  static readonly id = 'bazel';
+
+  static readonly bazelCentralRepoUrl =
+    'https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main';
+
+  override readonly defaultRegistryUrls = [BazelDatasource.bazelCentralRepoUrl];
+  override readonly customRegistrySupport = true;
+  override readonly caching = true;
+
+  static packageMetadataPath(packageName: string): string {
+    return `/modules/${packageName}/metadata.json`;
+  }
+
+  constructor() {
+    super(BazelDatasource.id);
+  }
+
+  @cache({
+    namespace: `datasource-${BazelDatasource.id}`,
+    key: ({ registryUrl, packageName }: GetReleasesConfig) =>
+      `${registryUrl!}:${packageName}`,
+  })
+  async getReleases({
+    registryUrl,
+    packageName,
+  }: GetReleasesConfig): Promise<ReleaseResult | null> {
+    const path = BazelDatasource.packageMetadataPath(packageName);
+    const url = joinUrlParts(registryUrl!, path);
+
+    const result: ReleaseResult = { releases: [] };
+    try {
+      const { body: metadata } = await this.http.getJson(
+        url,
+        BazelModuleMetadata
+      );
+      result.releases = metadata.versions
+        .map((v) => new BzlmodVersion(v))
+        .sort(BzlmodVersion.defaultCompare)
+        .map((bv) => {
+          const release: Release = { version: bv.original };
+          if (is.truthy(metadata.yanked_versions[bv.original])) {
+            release.isDeprecated = true;
+          }
+          return release;
+        });
+    } catch (err) {
+      // istanbul ignore else: not testable with nock
+      if (err instanceof HttpError) {
+        if (err.response?.statusCode === 404) {
+          return null;
+        }
+        throw new ExternalHostError(err);
+      }
+      this.handleGenericErrors(err);
+    }
+
+    return result.releases.length ? result : null;
+  }
+}
diff --git a/lib/modules/datasource/bazel/readme.md b/lib/modules/datasource/bazel/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..e4282e3d45c672820a43653b8091e23d8d42445f
--- /dev/null
+++ b/lib/modules/datasource/bazel/readme.md
@@ -0,0 +1 @@
+The `bazel` datasource is designed to query one or more [Bazel registries](https://bazel.build/external/registry) using the first successful result.
diff --git a/lib/modules/datasource/bazel/schema.spec.ts b/lib/modules/datasource/bazel/schema.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..832133baf11ab79c64f07ea278eebb6fe0eb74f1
--- /dev/null
+++ b/lib/modules/datasource/bazel/schema.spec.ts
@@ -0,0 +1,12 @@
+import { Fixtures } from '../../../../test/fixtures';
+import { BazelModuleMetadata } from './schema';
+
+describe('modules/datasource/bazel/schema', () => {
+  describe('BazelModuleMetadata', () => {
+    it('parses metadata', () => {
+      const metadataJson = Fixtures.get('metadata-with-yanked-versions.json');
+      const metadata = BazelModuleMetadata.parse(JSON.parse(metadataJson));
+      expect(metadata.versions).toHaveLength(4);
+    });
+  });
+});
diff --git a/lib/modules/datasource/bazel/schema.ts b/lib/modules/datasource/bazel/schema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4315d260c4c93ac52cf9f341af6520eea2da8f98
--- /dev/null
+++ b/lib/modules/datasource/bazel/schema.ts
@@ -0,0 +1,6 @@
+import { z } from 'zod';
+
+export const BazelModuleMetadata = z.object({
+  versions: z.array(z.string()),
+  yanked_versions: z.record(z.string(), z.string()),
+});
diff --git a/lib/modules/versioning/bazel-module/bzlmod-version.ts b/lib/modules/versioning/bazel-module/bzlmod-version.ts
index dcc52d6e690564a32a34589806cf9fdda4b0c961..42b315ed6724ff9857f4397d62a0b6ad9dbefa42 100644
--- a/lib/modules/versioning/bazel-module/bzlmod-version.ts
+++ b/lib/modules/versioning/bazel-module/bzlmod-version.ts
@@ -199,6 +199,7 @@ interface VersionRegexResult {
  * It signifies that there is a NonRegistryOverride for a module.
  */
 export class BzlmodVersion {
+  readonly original: string;
   readonly release: VersionPart;
   readonly prerelease: VersionPart;
   readonly build: VersionPart;
@@ -214,6 +215,7 @@ export class BzlmodVersion {
    *     values.
    */
   constructor(version: string) {
+    this.original = version;
     if (version === '') {
       this.release = VersionPart.create();
       this.prerelease = VersionPart.create();