diff --git a/lib/datasource/api.ts b/lib/datasource/api.ts
index cdcc24f13bb13cdb7ed5a5604675600993767122..3dd5e93b2f3f566b69dee9d1d5d6c8746b0cf31e 100644
--- a/lib/datasource/api.ts
+++ b/lib/datasource/api.ts
@@ -14,7 +14,7 @@ import * as gitlabTags from './gitlab-tags';
 import * as go from './go';
 import { GradleVersionDatasource } from './gradle-version';
 import { HelmDatasource } from './helm';
-import * as hex from './hex';
+import { HexDatasource } from './hex';
 import * as jenkinsPlugins from './jenkins-plugins';
 import * as maven from './maven';
 import * as npm from './npm';
@@ -51,7 +51,7 @@ api.set('gitlab-tags', gitlabTags);
 api.set('go', go);
 api.set('gradle-version', new GradleVersionDatasource());
 api.set('helm', new HelmDatasource());
-api.set('hex', hex);
+api.set('hex', new HexDatasource());
 api.set('jenkins-plugins', jenkinsPlugins);
 api.set('maven', maven);
 api.set('npm', npm);
diff --git a/lib/datasource/hex/index.spec.ts b/lib/datasource/hex/index.spec.ts
index d6f453018a816a11c511845b7bb85899d395b2f7..d5bd072d0d0ca53ea725dcc8aa19eb835dfeed84 100644
--- a/lib/datasource/hex/index.spec.ts
+++ b/lib/datasource/hex/index.spec.ts
@@ -3,7 +3,7 @@ import * as httpMock from '../../../test/http-mock';
 import { getName, loadJsonFixture } from '../../../test/util';
 import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages';
 import * as _hostRules from '../../util/host-rules';
-import { id as datasource } from '.';
+import { HexDatasource } from '.';
 
 const hostRules: any = _hostRules;
 
@@ -12,6 +12,7 @@ const res1 = loadJsonFixture('certifi.json');
 jest.mock('../../util/host-rules');
 
 const baseUrl = 'https://hex.pm/api/packages/';
+const datasource = HexDatasource.id;
 
 describe(getName(), () => {
   beforeEach(() => {
diff --git a/lib/datasource/hex/index.ts b/lib/datasource/hex/index.ts
index 9704251be5b19b4c365bd08434ac9d4a4b6cbe05..4a0fde477e44f54c379573bace3147a7cce79256 100644
--- a/lib/datasource/hex/index.ts
+++ b/lib/datasource/hex/index.ts
@@ -1,30 +1,46 @@
 import { logger } from '../../logger';
-import { ExternalHostError } from '../../types/errors/external-host-error';
-import { Http } from '../../util/http';
+import { cache } from '../../util/cache/package/decorator';
+import type { HttpResponse } from '../../util/http';
 import * as hexVersioning from '../../versioning/hex';
+import { Datasource } from '../datasource';
 import type { GetReleasesConfig, ReleaseResult } from '../types';
 import type { HexRelease } from './types';
 
-export const id = 'hex';
-export const defaultRegistryUrls = ['https://hex.pm/'];
-export const customRegistrySupport = false;
-export const defaultVersioning = hexVersioning.id;
+export class HexDatasource extends Datasource {
+  static readonly id = 'hex';
 
-const http = new Http(id);
+  constructor() {
+    super(HexDatasource.id);
+  }
+
+  override readonly defaultRegistryUrls = ['https://hex.pm/'];
+
+  override readonly customRegistrySupport = false;
+
+  override readonly defaultVersioning = hexVersioning.id;
 
-export async function getReleases({
-  lookupName,
-  registryUrl,
-}: GetReleasesConfig): Promise<ReleaseResult | null> {
-  // Get dependency name from lookupName.
-  // If the dependency is private lookupName contains organization name as following:
-  // hexPackageName:organizationName
-  // hexPackageName is used to pass it in hex dep url
-  // organizationName is used for accessing to private deps
-  const hexPackageName = lookupName.split(':')[0];
-  const hexUrl = `${registryUrl}api/packages/${hexPackageName}`;
-  try {
-    const response = await http.getJson<HexRelease>(hexUrl);
+  @cache({
+    namespace: `datasource-${HexDatasource.id}`,
+    key: ({ lookupName }: GetReleasesConfig) => lookupName,
+  })
+  async getReleases({
+    lookupName,
+    registryUrl,
+  }: GetReleasesConfig): Promise<ReleaseResult | null> {
+    // Get dependency name from lookupName.
+    // If the dependency is private lookupName contains organization name as following:
+    // hexPackageName:organizationName
+    // hexPackageName is used to pass it in hex dep url
+    // organizationName is used for accessing to private deps
+    const hexPackageName = lookupName.split(':')[0];
+    const hexUrl = `${registryUrl}api/packages/${hexPackageName}`;
+
+    let response: HttpResponse<HexRelease>;
+    try {
+      response = await this.http.getJson<HexRelease>(hexUrl);
+    } catch (err) {
+      this.handleGenericErrors(err);
+    }
 
     const hexRelease: HexRelease = response.body;
 
@@ -60,13 +76,5 @@ export async function getReleases({
     }
 
     return result;
-  } catch (err) {
-    if (
-      err.statusCode === 429 ||
-      (err.statusCode >= 500 && err.statusCode < 600)
-    ) {
-      throw new ExternalHostError(err);
-    }
-    throw err;
   }
 }
diff --git a/lib/manager/mix/extract.ts b/lib/manager/mix/extract.ts
index b5b3cf33feabeeee17784786a8dfaf8336f49ac9..5eacc93ff065a725759651126edd3819dec0c725 100644
--- a/lib/manager/mix/extract.ts
+++ b/lib/manager/mix/extract.ts
@@ -1,4 +1,4 @@
-import * as datasourceHex from '../../datasource/hex';
+import { HexDatasource } from '../../datasource/hex';
 import { logger } from '../../logger';
 import { SkipReason } from '../../types';
 import { getSiblingFileName, localPathExists } from '../../util/fs';
@@ -39,9 +39,9 @@ export async function extractPackageFile(
             managerData: {},
           };
 
-          dep.datasource = datasource || datasourceHex.id;
+          dep.datasource = datasource || HexDatasource.id;
 
-          if (dep.datasource === datasourceHex.id) {
+          if (dep.datasource === HexDatasource.id) {
             dep.currentValue = currentValue;
             dep.lookupName = depName;
           }
@@ -50,7 +50,7 @@ export async function extractPackageFile(
             dep.lookupName += ':' + organization;
           }
 
-          if (dep.datasource !== datasourceHex.id) {
+          if (dep.datasource !== HexDatasource.id) {
             dep.skipReason = SkipReason.NonHexDeptypes;
           }