diff --git a/lib/datasource/api.ts b/lib/datasource/api.ts
index d7619f3ac6c2ec700fd3855210e46f50d3cd0cd6..89b88645fc02493aba73b28194f57d35019905da 100644
--- a/lib/datasource/api.ts
+++ b/lib/datasource/api.ts
@@ -19,7 +19,7 @@ import * as go from './go';
 import { GradleVersionDatasource } from './gradle-version';
 import { HelmDatasource } from './helm';
 import { HexDatasource } from './hex';
-import * as jenkinsPlugins from './jenkins-plugins';
+import { JenkinsPluginsDatasource } from './jenkins-plugins';
 import * as maven from './maven';
 import { NodeDatasource } from './node';
 import * as npm from './npm';
@@ -61,7 +61,7 @@ api.set('go', go);
 api.set('gradle-version', new GradleVersionDatasource());
 api.set('helm', new HelmDatasource());
 api.set('hex', new HexDatasource());
-api.set('jenkins-plugins', jenkinsPlugins);
+api.set('jenkins-plugins', new JenkinsPluginsDatasource());
 api.set('maven', maven);
 api.set('npm', npm);
 api.set(NodeDatasource.id, new NodeDatasource());
diff --git a/lib/datasource/jenkins-plugins/common.ts b/lib/datasource/jenkins-plugins/common.ts
deleted file mode 100644
index 22acbbb5ab896de036e471a6df06b7aeba74d4c1..0000000000000000000000000000000000000000
--- a/lib/datasource/jenkins-plugins/common.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const id = 'jenkins-plugins';
diff --git a/lib/datasource/jenkins-plugins/get.ts b/lib/datasource/jenkins-plugins/get.ts
deleted file mode 100644
index a80c96ef2c32a71633b1b120dbf18052b448de62..0000000000000000000000000000000000000000
--- a/lib/datasource/jenkins-plugins/get.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-import { logger } from '../../logger';
-import { ExternalHostError } from '../../types/errors/external-host-error';
-import { clone } from '../../util/clone';
-import { getElapsedMinutes } from '../../util/date';
-import { Http } from '../../util/http';
-import type { GetReleasesConfig, Release, ReleaseResult } from '../types';
-import { id } from './common';
-import type {
-  JenkinsCache,
-  JenkinsCacheTypes,
-  JenkinsPluginsInfoResponse,
-  JenkinsPluginsVersionsResponse,
-} from './types';
-
-const http = new Http(id);
-
-const packageInfoUrl =
-  'https://updates.jenkins.io/current/update-center.actual.json';
-const packageVersionsUrl =
-  'https://updates.jenkins.io/current/plugin-versions.json';
-
-function hasCacheExpired(cache: JenkinsCache<JenkinsCacheTypes>): boolean {
-  return getElapsedMinutes(cache.lastSync) >= cache.cacheTimeMin;
-}
-
-async function updateJenkinsCache(
-  cache: JenkinsCache<JenkinsCacheTypes>,
-  updateHandler: () => Promise<void>
-): Promise<void> {
-  if (hasCacheExpired(cache)) {
-    cache.updatePromise =
-      // eslint-disable-next-line @typescript-eslint/no-misused-promises
-      cache.updatePromise || updateHandler();
-    await cache.updatePromise;
-
-    cache.updatePromise = null;
-  }
-}
-
-function updateJenkinsPluginInfoCacheCallback(
-  response: JenkinsPluginsInfoResponse,
-  cache: JenkinsCache<ReleaseResult>
-): void {
-  for (const name of Object.keys(response.plugins || [])) {
-    cache.cache[name] = {
-      releases: [], // releases are stored in another cache
-      sourceUrl: response.plugins[name]?.scm,
-    };
-  }
-}
-
-function updateJenkinsPluginVersionsCacheCallback(
-  response: JenkinsPluginsVersionsResponse,
-  cache: JenkinsCache<Release[]>
-): void {
-  const plugins = response.plugins;
-  for (const name of Object.keys(plugins || [])) {
-    cache.cache[name] = Object.keys(plugins[name]).map((version) => ({
-      version,
-      downloadUrl: plugins[name][version]?.url,
-      releaseTimestamp: plugins[name][version]?.buildDate
-        ? new Date(plugins[name][version].buildDate + ' UTC')
-        : null,
-    }));
-  }
-}
-
-async function getJenkinsUpdateCenterResponse<T>(
-  cache: JenkinsCache<JenkinsCacheTypes>
-): Promise<T> {
-  let response: T;
-
-  const options = {
-    headers: {
-      'Accept-Encoding': 'gzip, deflate, br',
-    },
-  };
-
-  try {
-    logger.debug(`jenkins-plugins: Fetching Jenkins plugins ${cache.name}`);
-    const startTime = Date.now();
-    response = (await http.getJson<T>(cache.dataUrl, options)).body;
-    const durationMs = Math.round(Date.now() - startTime);
-    logger.debug(
-      { durationMs },
-      `jenkins-plugins: Fetched Jenkins plugins ${cache.name}`
-    );
-  } catch (err) /* istanbul ignore next */ {
-    cache.cache = Object.create(null);
-    throw new ExternalHostError(
-      new Error(`jenkins-plugins: Fetch plugins ${cache.name} error`)
-    );
-  }
-
-  return response;
-}
-
-async function updateJenkinsPluginCache<T>(
-  cache: JenkinsCache<JenkinsCacheTypes>,
-
-  callback: (resp: T, cache: JenkinsCache<any>) => void
-): Promise<void> {
-  const response = await getJenkinsUpdateCenterResponse<T>(cache);
-  if (response) {
-    callback(response, cache);
-  }
-  cache.lastSync = new Date();
-}
-
-const pluginInfoCache: JenkinsCache<ReleaseResult> = {
-  name: 'info',
-  dataUrl: packageInfoUrl,
-  lastSync: new Date('2000-01-01'),
-  cacheTimeMin: 1440,
-  cache: Object.create(null),
-};
-
-const pluginVersionsCache: JenkinsCache<Release[]> = {
-  name: 'versions',
-  dataUrl: packageVersionsUrl,
-  lastSync: new Date('2000-01-01'),
-  cacheTimeMin: 60,
-  cache: Object.create(null),
-};
-
-async function updateJenkinsPluginInfoCache(): Promise<void> {
-  await updateJenkinsPluginCache<JenkinsPluginsInfoResponse>(
-    pluginInfoCache,
-    updateJenkinsPluginInfoCacheCallback
-  );
-}
-
-async function updateJenkinsPluginVersionsCache(): Promise<void> {
-  await updateJenkinsPluginCache<JenkinsPluginsVersionsResponse>(
-    pluginVersionsCache,
-    updateJenkinsPluginVersionsCacheCallback
-  );
-}
-
-export async function getJenkinsPluginDependency(
-  lookupName: string
-): Promise<ReleaseResult | null> {
-  logger.debug(`getJenkinsDependency(${lookupName})`);
-  await updateJenkinsCache(pluginInfoCache, updateJenkinsPluginInfoCache);
-  await updateJenkinsCache(
-    pluginVersionsCache,
-    updateJenkinsPluginVersionsCache
-  );
-
-  const plugin = pluginInfoCache.cache[lookupName];
-  if (!plugin) {
-    return null;
-  }
-
-  const result = clone(plugin);
-  const releases = pluginVersionsCache.cache[lookupName];
-  result.releases = releases ? clone(releases) : [];
-  return result;
-}
-
-export function getReleases({
-  lookupName,
-}: GetReleasesConfig): Promise<ReleaseResult | null> {
-  return getJenkinsPluginDependency(lookupName);
-}
-
-function resetJenkinsCache(cache: JenkinsCache<JenkinsCacheTypes>): void {
-  cache.lastSync = new Date('2000-01-01');
-  cache.cache = Object.create(null);
-}
-
-// Note: use only for tests
-export function resetCache(): void {
-  resetJenkinsCache(pluginInfoCache);
-  resetJenkinsCache(pluginVersionsCache);
-}
diff --git a/lib/datasource/jenkins-plugins/index.spec.ts b/lib/datasource/jenkins-plugins/index.spec.ts
index 9057c6443890156c5fe2f720af33b94ab80bf630..429bc6d4402282cff89f139b8e70957f8f2d881b 100644
--- a/lib/datasource/jenkins-plugins/index.spec.ts
+++ b/lib/datasource/jenkins-plugins/index.spec.ts
@@ -2,34 +2,24 @@ import { getPkgReleases } from '..';
 import * as httpMock from '../../../test/http-mock';
 import { loadJsonFixture } from '../../../test/util';
 import * as versioning from '../../versioning/docker';
-import { resetCache } from './get';
-import * as jenkins from '.';
+import { JenkinsPluginsDatasource } from '.';
 
 const jenkinsPluginsVersions = loadJsonFixture('plugin-versions.json');
 const jenkinsPluginsInfo = loadJsonFixture('update-center.actual.json');
 
 describe('datasource/jenkins-plugins/index', () => {
   describe('getReleases', () => {
-    const SKIP_CACHE = process.env.RENOVATE_SKIP_CACHE;
-
     const params = {
       versioning: versioning.id,
-      datasource: jenkins.id,
+      datasource: JenkinsPluginsDatasource.id,
       depName: 'email-ext',
       registryUrls: ['https://updates.jenkins.io/'],
     };
 
-    beforeEach(() => {
-      resetCache();
-      process.env.RENOVATE_SKIP_CACHE = 'true';
-      jest.resetAllMocks();
-    });
-
     afterEach(() => {
       if (!httpMock.allUsed()) {
         throw new Error('Not all http mocks have been used!');
       }
-      process.env.RENOVATE_SKIP_CACHE = SKIP_CACHE;
     });
 
     it('returns null for a package miss', async () => {
@@ -41,11 +31,6 @@ describe('datasource/jenkins-plugins/index', () => {
         .get('/current/update-center.actual.json')
         .reply(200, jenkinsPluginsInfo);
 
-      httpMock
-        .scope('https://updates.jenkins.io')
-        .get('/current/plugin-versions.json')
-        .reply(200, jenkinsPluginsVersions);
-
       expect(await getPkgReleases(newparams)).toBeNull();
     });
 
@@ -60,7 +45,7 @@ describe('datasource/jenkins-plugins/index', () => {
         .get('/current/plugin-versions.json')
         .reply(200, jenkinsPluginsVersions);
 
-      let res = await getPkgReleases(params);
+      const res = await getPkgReleases(params);
       expect(res.releases).toHaveLength(75);
       expect(res).toMatchSnapshot();
 
@@ -74,10 +59,6 @@ describe('datasource/jenkins-plugins/index', () => {
       expect(
         res.releases.find((release) => release.version === '12.98')
       ).toBeUndefined();
-
-      // check that caching is working and no http requests are done after the first call to getPkgReleases
-      res = await getPkgReleases(params);
-      expect(res.releases).toHaveLength(75);
     });
 
     it('returns package releases for a hit for info and miss for releases', async () => {
@@ -106,11 +87,6 @@ describe('datasource/jenkins-plugins/index', () => {
         .get('/current/update-center.actual.json')
         .reply(200, '{}');
 
-      httpMock
-        .scope('https://updates.jenkins.io')
-        .get('/current/plugin-versions.json')
-        .reply(200, '{}');
-
       expect(await getPkgReleases(params)).toBeNull();
     });
   });
diff --git a/lib/datasource/jenkins-plugins/index.ts b/lib/datasource/jenkins-plugins/index.ts
index 5790e0e57ca2b40a74681a34e779136e8e7d9f35..dfafe85cb833b8b90815060a0ca9acf2f8fc0a0a 100644
--- a/lib/datasource/jenkins-plugins/index.ts
+++ b/lib/datasource/jenkins-plugins/index.ts
@@ -1,5 +1,102 @@
-export { id } from './common';
-export { getReleases } from './get';
-export const customRegistrySupport = true;
-export const defaultRegistryUrls = ['https://updates.jenkins.io'];
-export const registryStrategy = 'hunt';
+import { logger } from '../../logger';
+import { cache } from '../../util/cache/package/decorator';
+import { clone } from '../../util/clone';
+import { Datasource } from '../datasource';
+import type { GetReleasesConfig, Release, ReleaseResult } from '../types';
+import type {
+  JenkinsPluginsInfoResponse,
+  JenkinsPluginsVersionsResponse,
+} from './types';
+
+export class JenkinsPluginsDatasource extends Datasource {
+  static readonly id = 'jenkins-plugins';
+
+  constructor() {
+    super(JenkinsPluginsDatasource.id);
+  }
+
+  override readonly defaultRegistryUrls = ['https://updates.jenkins.io'];
+
+  override readonly registryStrategy = 'hunt';
+
+  private static readonly packageInfoUrl =
+    'https://updates.jenkins.io/current/update-center.actual.json';
+  private static readonly packageVersionsUrl =
+    'https://updates.jenkins.io/current/plugin-versions.json';
+
+  async getReleases({
+    lookupName,
+  }: GetReleasesConfig): Promise<ReleaseResult | null> {
+    const plugins = await this.getJenkinsPluginInfo();
+    const plugin = plugins[lookupName];
+    if (!plugin) {
+      return null;
+    }
+
+    const result = clone(plugin);
+    const versions = await this.getJenkinsPluginVersions();
+    const releases = versions[lookupName];
+    result.releases = releases ? clone(releases) : [];
+    return result;
+  }
+
+  @cache({
+    namespace: JenkinsPluginsDatasource.id,
+    key: 'info',
+    ttlMinutes: 1440,
+  })
+  async getJenkinsPluginInfo(): Promise<Record<string, ReleaseResult>> {
+    const { plugins } =
+      await this.getJenkinsUpdateCenterResponse<JenkinsPluginsInfoResponse>(
+        JenkinsPluginsDatasource.packageInfoUrl
+      );
+
+    const info: Record<string, ReleaseResult> = {};
+    for (const name of Object.keys(plugins ?? [])) {
+      info[name] = {
+        releases: [], // releases
+        sourceUrl: plugins[name]?.scm,
+      };
+    }
+    return info;
+  }
+
+  @cache({ namespace: JenkinsPluginsDatasource.id, key: 'versions' })
+  async getJenkinsPluginVersions(): Promise<Record<string, Release[]>> {
+    const { plugins } =
+      await this.getJenkinsUpdateCenterResponse<JenkinsPluginsVersionsResponse>(
+        JenkinsPluginsDatasource.packageVersionsUrl
+      );
+
+    const versions: Record<string, Release[]> = {};
+    for (const name of Object.keys(plugins ?? [])) {
+      versions[name] = Object.keys(plugins[name]).map((version) => ({
+        version,
+        downloadUrl: plugins[name][version]?.url,
+        releaseTimestamp: plugins[name][version]?.buildDate
+          ? new Date(`${plugins[name][version].buildDate} UTC`)
+          : null,
+      }));
+    }
+    return versions;
+  }
+
+  private async getJenkinsUpdateCenterResponse<T>(url: string): Promise<T> {
+    let response: T;
+
+    try {
+      logger.debug(`jenkins-plugins: Fetching Jenkins plugins from ${url}`);
+      const startTime = Date.now();
+      response = (await this.http.getJson<T>(url)).body;
+      const durationMs = Math.round(Date.now() - startTime);
+      logger.debug(
+        { durationMs },
+        `jenkins-plugins: Fetched Jenkins plugins from ${url}`
+      );
+    } catch (err) /* istanbul ignore next */ {
+      this.handleGenericErrors(err);
+    }
+
+    return response;
+  }
+}
diff --git a/lib/datasource/jenkins-plugins/types.ts b/lib/datasource/jenkins-plugins/types.ts
index 6c67a9bca14b95743e1bd7dec00e7a998199249a..16e730a3094689adef7dc7d4a02caaf2746ca12a 100644
--- a/lib/datasource/jenkins-plugins/types.ts
+++ b/lib/datasource/jenkins-plugins/types.ts
@@ -1,16 +1,3 @@
-import type { Release, ReleaseResult } from '../types';
-
-export type JenkinsCacheTypes = ReleaseResult | Release[];
-
-export interface JenkinsCache<T> {
-  name: string;
-  dataUrl: string;
-  lastSync: Date;
-  cacheTimeMin: number;
-  cache: Record<string, T>;
-  updatePromise?: Promise<void> | undefined;
-}
-
 export interface JenkinsPluginInfo {
   name: string;
   scm?: string;
diff --git a/lib/manager/jenkins/extract.ts b/lib/manager/jenkins/extract.ts
index c5e9d013eb21cc6ddf006bb9181ce3b862074250..3dcc58b0ce5a33734ecf53dc42c16a148d54fc2b 100644
--- a/lib/manager/jenkins/extract.ts
+++ b/lib/manager/jenkins/extract.ts
@@ -1,5 +1,5 @@
 import { load } from 'js-yaml';
-import * as datasourceJenkins from '../../datasource/jenkins-plugins';
+import { JenkinsPluginsDatasource } from '../../datasource/jenkins-plugins';
 import { logger } from '../../logger';
 import { SkipReason } from '../../types';
 import { isSkipComment } from '../../util/ignore';
@@ -12,7 +12,7 @@ const YamlExtension = regEx(/\.ya?ml$/);
 
 function getDependency(plugin: JenkinsPlugin): PackageDependency {
   const dep: PackageDependency = {
-    datasource: datasourceJenkins.id,
+    datasource: JenkinsPluginsDatasource.id,
     versioning: dockerVersioning.id,
     depName: plugin.artifactId,
   };
diff --git a/tsconfig.strict.json b/tsconfig.strict.json
index 50bda1274807772b4df58bb328bb924af685d210..dbadb581e39e05fa98d070abdae10215e60ebf06 100644
--- a/tsconfig.strict.json
+++ b/tsconfig.strict.json
@@ -31,7 +31,6 @@
     "./lib/datasource/gitlab-tags/types.ts",
     "./lib/datasource/gradle-version/types.ts",
     "./lib/datasource/hex/types.ts",
-    "./lib/datasource/jenkins-plugins/common.ts",
     "./lib/datasource/maven/common.ts",
     "./lib/datasource/node/common.ts",
     "./lib/datasource/node/types.ts",