From 06d8c270433206eecba4f60635f669cd8c5393c1 Mon Sep 17 00:00:00 2001
From: Maksim <m.v.sharipov@gmail.com>
Date: Tue, 23 Nov 2021 21:10:45 +0100
Subject: [PATCH] refactor: global config (#12743)

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
---
 lib/config/decrypt.spec.ts                    | 23 +++---
 lib/config/decrypt.ts                         |  4 +-
 lib/config/global.ts                          | 78 +++++++++++--------
 lib/config/migration.spec.ts                  |  4 +-
 lib/config/migration.ts                       |  4 +-
 lib/config/presets/npm/index.spec.ts          |  4 +-
 lib/datasource/crate/index.spec.ts            | 16 ++--
 lib/datasource/crate/index.ts                 |  4 +-
 lib/datasource/npm/index.spec.ts              |  8 +-
 lib/datasource/npm/npmrc.spec.ts              |  6 +-
 lib/datasource/npm/npmrc.ts                   |  4 +-
 lib/manager/batect/extract.spec.ts            |  6 +-
 lib/manager/bundler/artifacts.spec.ts         | 22 +++---
 lib/manager/cargo/artifacts.spec.ts           |  8 +-
 lib/manager/cargo/extract.spec.ts             |  6 +-
 lib/manager/cocoapods/artifacts.spec.ts       | 16 ++--
 lib/manager/cocoapods/extract.spec.ts         |  6 +-
 lib/manager/composer/artifacts.spec.ts        | 20 +++--
 lib/manager/composer/utils.spec.ts            | 12 +--
 lib/manager/composer/utils.ts                 |  6 +-
 lib/manager/git-submodules/extract.spec.ts    |  4 +-
 lib/manager/git-submodules/extract.ts         |  4 +-
 lib/manager/git-submodules/update.spec.ts     |  6 +-
 lib/manager/git-submodules/update.ts          |  4 +-
 lib/manager/gitlabci/extract.spec.ts          |  6 +-
 lib/manager/gomod/artifacts.spec.ts           | 26 +++----
 lib/manager/gomod/artifacts.ts                |  4 +-
 .../gradle-wrapper/artifacts-real.spec.ts     | 10 +--
 lib/manager/gradle-wrapper/artifacts.spec.ts  |  8 +-
 lib/manager/gradle-wrapper/artifacts.ts       |  4 +-
 lib/manager/gradle-wrapper/util.spec.ts       | 10 +--
 lib/manager/gradle-wrapper/utils.ts           |  6 +-
 .../gradle/deep/gradle-updates-report.spec.ts |  4 +-
 lib/manager/gradle/deep/index-real.spec.ts    |  6 +-
 lib/manager/gradle/deep/index.spec.ts         | 12 +--
 lib/manager/gradle/deep/index.ts              |  4 +-
 lib/manager/gradle/deep/utils.ts              |  6 +-
 lib/manager/helmv3/artifacts.spec.ts          | 10 +--
 lib/manager/jsonnet-bundler/artifacts.spec.ts |  4 +-
 lib/manager/mix/artifacts.spec.ts             | 12 +--
 lib/manager/mix/extract.spec.ts               |  4 +-
 lib/manager/npm/extract/index.ts              |  4 +-
 lib/manager/npm/extract/pnpm.spec.ts          |  4 +-
 lib/manager/npm/post-update/index.ts          |  8 +-
 lib/manager/npm/post-update/lerna.spec.ts     |  4 +-
 lib/manager/npm/post-update/lerna.ts          |  6 +-
 lib/manager/npm/post-update/npm.ts            |  6 +-
 lib/manager/npm/post-update/pnpm.ts           |  6 +-
 lib/manager/npm/post-update/yarn.ts           |  6 +-
 lib/manager/nuget/artifacts.spec.ts           | 10 +--
 lib/manager/nuget/artifacts.ts                |  4 +-
 lib/manager/nuget/extract.spec.ts             |  6 +-
 lib/manager/nuget/extract.ts                  |  4 +-
 lib/manager/pip-compile/artifacts.spec.ts     |  8 +-
 .../pip_requirements/artifacts.spec.ts        |  4 +-
 lib/manager/pip_requirements/extract.spec.ts  |  8 +-
 lib/manager/pip_requirements/extract.ts       |  4 +-
 lib/manager/pipenv/artifacts.spec.ts          | 12 +--
 lib/manager/poetry/artifacts.spec.ts          |  8 +-
 lib/manager/terraform/extract.spec.ts         |  4 +-
 lib/manager/terraform/lockfile/hash.spec.ts   |  4 +-
 lib/manager/terraform/lockfile/index.spec.ts  |  4 +-
 lib/util/cache/repository/index.spec.ts       |  4 +-
 lib/util/cache/repository/index.ts            |  4 +-
 lib/util/exec/docker/index.spec.ts            |  8 +-
 lib/util/exec/docker/index.ts                 |  6 +-
 lib/util/exec/env.spec.ts                     |  4 +-
 lib/util/exec/env.ts                          |  4 +-
 lib/util/exec/index.spec.ts                   | 16 ++--
 lib/util/exec/index.ts                        | 12 +--
 lib/util/fs/index.spec.ts                     | 12 +--
 lib/util/fs/index.ts                          | 20 ++---
 lib/util/git/index.spec.ts                    |  4 +-
 lib/util/git/index.ts                         |  8 +-
 lib/util/json-writer/editor-config.spec.ts    |  4 +-
 lib/util/json-writer/editor-config.ts         |  4 +-
 lib/util/template/index.ts                    |  4 +-
 lib/workers/branch/artifacts.spec.ts          |  6 +-
 lib/workers/branch/artifacts.ts               |  4 +-
 lib/workers/branch/automerge.spec.ts          |  6 +-
 lib/workers/branch/automerge.ts               |  4 +-
 lib/workers/branch/commit.spec.ts             |  6 +-
 lib/workers/branch/commit.ts                  |  4 +-
 .../branch/execute-post-upgrade-commands.ts   |  8 +-
 lib/workers/branch/handle-existing.ts         |  6 +-
 lib/workers/branch/index.spec.ts              | 26 +++----
 lib/workers/branch/index.ts                   |  8 +-
 lib/workers/branch/lock-files/index.spec.ts   |  6 +-
 lib/workers/branch/reuse.ts                   |  4 +-
 lib/workers/pr/automerge.ts                   |  6 +-
 lib/workers/pr/index.ts                       | 14 ++--
 .../repository/dependency-dashboard.spec.ts   |  6 +-
 .../repository/dependency-dashboard.ts        |  8 +-
 lib/workers/repository/error-config.spec.ts   |  8 +-
 lib/workers/repository/error-config.ts        |  6 +-
 lib/workers/repository/finalise/prune.spec.ts | 10 +--
 lib/workers/repository/finalise/prune.ts      |  8 +-
 lib/workers/repository/index.spec.ts          |  4 +-
 lib/workers/repository/index.ts               |  6 +-
 lib/workers/repository/init/cache.spec.ts     |  4 +-
 lib/workers/repository/init/index.spec.ts     |  6 +-
 .../onboarding/branch/config.spec.ts          |  4 +-
 .../repository/onboarding/branch/create.ts    |  4 +-
 .../repository/onboarding/branch/index.ts     |  4 +-
 .../onboarding/branch/rebase.spec.ts          |  4 +-
 .../repository/onboarding/branch/rebase.ts    |  4 +-
 .../repository/onboarding/pr/index.spec.ts    |  8 +-
 lib/workers/repository/onboarding/pr/index.ts |  8 +-
 lib/workers/repository/process/deprecated.ts  |  4 +-
 109 files changed, 441 insertions(+), 422 deletions(-)

diff --git a/lib/config/decrypt.spec.ts b/lib/config/decrypt.spec.ts
index 2d6d215125..3a51e8dcc2 100644
--- a/lib/config/decrypt.spec.ts
+++ b/lib/config/decrypt.spec.ts
@@ -1,6 +1,6 @@
 import { loadFixture } from '../../test/util';
 import { decryptConfig } from './decrypt';
-import { setGlobalConfig } from './global';
+import { GlobalConfig } from './global';
 import type { RenovateConfig } from './types';
 
 const privateKey = loadFixture('private.pem', '.');
@@ -12,7 +12,7 @@ describe('config/decrypt', () => {
     let config: RenovateConfig;
     beforeEach(() => {
       config = {};
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
     it('returns empty with no privateKey', async () => {
       delete config.encrypted;
@@ -27,19 +27,22 @@ describe('config/decrypt', () => {
     });
     it('handles invalid encrypted type', async () => {
       config.encrypted = 1;
-      setGlobalConfig({ privateKey });
+      GlobalConfig.set({ privateKey });
       const res = await decryptConfig(config, repository);
       expect(res.encrypted).toBeUndefined();
     });
     it('handles invalid encrypted value', async () => {
       config.encrypted = { a: 1 };
-      setGlobalConfig({ privateKey, privateKeyOld: 'invalid-key' });
+      GlobalConfig.set({ privateKey, privateKeyOld: 'invalid-key' });
       await expect(decryptConfig(config, repository)).rejects.toThrow(
         'config-validation'
       );
     });
     it('replaces npm token placeholder in npmrc', async () => {
-      setGlobalConfig({ privateKey: 'invalid-key', privateKeyOld: privateKey }); // test old key failover
+      GlobalConfig.set({
+        privateKey: 'invalid-key',
+        privateKeyOld: privateKey,
+      }); // test old key failover
       config.npmrc =
         '//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n';
       config.encrypted = {
@@ -54,7 +57,7 @@ describe('config/decrypt', () => {
       );
     });
     it('appends npm token in npmrc', async () => {
-      setGlobalConfig({ privateKey });
+      GlobalConfig.set({ privateKey });
       config.npmrc = 'foo=bar\n';
       config.encrypted = {
         npmToken:
@@ -66,7 +69,7 @@ describe('config/decrypt', () => {
       expect(res.npmrc).toMatchSnapshot();
     });
     it('decrypts nested', async () => {
-      setGlobalConfig({ privateKey });
+      GlobalConfig.set({ privateKey });
       config.packageFiles = [
         {
           packageFile: 'package.json',
@@ -93,7 +96,7 @@ describe('config/decrypt', () => {
       );
     });
     it('rejects invalid PGP message', async () => {
-      setGlobalConfig({ privateKey: privateKeyPgp });
+      GlobalConfig.set({ privateKey: privateKeyPgp });
       config.encrypted = {
         token:
           'long-but-wrong-wcFMAw+4H7SgaqGOAQ//ZNPgHJ4RQBdfFoDX8Ywe9UxqMlc8k6VasCszQ2JULh/BpEdKdgRUGNaKaeZ+oBKYDBmDwAD5V5FEMlsg+KO2gykp/p2BAwvKGtYK0MtxLh4h9yJbN7TrVnGO3/cC+Inp8exQt0gD6f1Qo/9yQ9NE4/BIbaSs2b2DgeIK7Ed8N675AuSo73UOa6o7t+9pKeAAK5TQwgSvolihbUs8zjnScrLZD+nhvL3y5gpAqK9y//a+bTu6xPA1jdLjsswoCUq/lfVeVsB2GWV2h6eex/0fRKgN7xxNgdMn0a7msrvumhTawP8mPisPY2AAsHRIgQ9vdU5HbOPdGoIwI9n9rMdIRn9Dy7/gcX9Ic+RP2WwS/KnPHLu/CveY4W5bYqYoikWtJs9HsBCyWFiHIRrJF+FnXwtKdoptRfxTfJIkBoLrV6fDIyKo79iL+xxzgrzWs77KEJUJfexZBEGBCnrV2o7mo3SU197S0qx7HNvqrmeCj8CLxq8opXC71TNa+XE6BQUVyhMFxtW9LNxZUHRiNzrTSikArT4hzjyr3f9cb0kZVcs6XJQsm1EskU3WXo7ETD7nsukS9GfbwMn7tfYidB/yHSHl09ih871BcgByDmEKKdmamcNilW2bmTAqB5JmtaYT5/H8jRQWo/VGrEqlmiA4KmwSv7SZPlDnaDFrmzmMZZDSRgHe5KWl283XLmSeE8J0NPqwFH3PeOv4fIbOjJrnbnFBwSAsgsMe2K4OyFDh2COfrho7s8EP1Kl5lBkYJ+VRreGRerdSu24',
@@ -133,7 +136,7 @@ describe('config/decrypt', () => {
       );
     });
     it('handles PGP org constraint', async () => {
-      setGlobalConfig({ privateKey: privateKeyPgp });
+      GlobalConfig.set({ privateKey: privateKeyPgp });
       config.encrypted = {
         token:
           'wcFMAw+4H7SgaqGOAQ/+Lz6RlbEymbnmMhrktuaGiDPWRNPEQFuMRwwYM6/B/r0JMZa9tskAA5RpyYKxGmJJeuRtlA8GkTw02GoZomlJf/KXJZ95FwSbkXMSRJRD8LJ2402Hw2TaOTaSvfamESnm8zhNo8cok627nkKQkyrpk64heVlU5LIbO2+UgYgbiSQjuXZiW+QuJ1hVRjx011FQgEYc59+22yuKYqd8rrni7TrVqhGRlHCAqvNAGjBI4H7uTFh0sP4auunT/JjxTeTkJoNu8KgS/LdrvISpO67TkQziZo9XD5FOzSN7N3e4f8vO4N4fpjgkIDH/9wyEYe0zYz34xMAFlnhZzqrHycRqzBJuMxGqlFQcKWp9IisLMoVJhLrnvbDLuwwcjeqYkhvODjSs7UDKwTE4X4WmvZr0x4kOclOeAAz/pM6oNVnjgWJd9SnYtoa67bZVkne0k6mYjVhosie8v8icijmJ4OyLZUGWnjZCRd/TPkzQUw+B0yvsop9FYGidhCI+4MVx6W5w7SRtCctxVfCjLpmU4kWaBUUJ5YIQ5xm55yxEYuAsQkxOAYDCMFlV8ntWStYwIG1FsBgJX6VPevXuPPMjWiPNedIpJwBH2PLB4blxMfzDYuCeaIqU4daDaEWxxpuFTTK9fLdJKuipwFG6rwE3OuijeSN+2SLszi834DXtUjQdikHSTQG392+oTmZCFPeffLk/OiV2VpdXF3gGL7sr5M9hOWIZ783q0vW1l6nAElZ7UA//kW+L6QRxbnBVTJK5eCmMY6RJmL76zjqC1jQ0FC10',
@@ -146,7 +149,7 @@ describe('config/decrypt', () => {
       );
     });
     it('handles PGP org/repo constraint', async () => {
-      setGlobalConfig({ privateKey: privateKeyPgp });
+      GlobalConfig.set({ privateKey: privateKeyPgp });
       config.encrypted = {
         token:
           'wcFMAw+4H7SgaqGOAQ//Wp7N0PaDZp0uOdwsc1CuqAq0UPcq+IQdHyKpJs3tHiCecXBHogy4P+rY9nGaUrVneCr4HexuKGuyJf1yl0ZqFffAUac5PjF8eDvjukQGOUq4aBlOogJCEefnuuVxVJx+NRR5iF1P6v57bmI1c+zoqZI/EQB30KU6O1BsdGPLUA/+R3dwCZd5Mbd36s34eYBasqcY9/QbqFcpElXMEPMse3kMCsVXPbZ+UMjtPJiBPUmtJq+ifnu1LzDrfshusSQMwgd/QNk7nEsijiYKllkWhHTP6g7zigvJ46x0h6AYS108YiuK3B9XUhXN9m05Ac6KTEEUdRI3E/dK2dQuRkLjXC8wceQm4A19Gm0uHoMIJYOCbiVoBCH6ayvKbZWZV5lZ4D1JbDNGmKeIj6OX9XWEMKiwTx0Xe89V7BdJzwIGrL0TCLtXuYWZ/R2k+UuBqtgzr44BsBqMpKUA0pcGBoqsEou1M05Ae9fJMF6ADezF5UQZPxT1hrMldiTp3p9iHGfWN2tKHeoW/8CqlIqg9JEkTc+Pl/L9E6ndy5Zjf097PvcmSGhxUQBE7XlrZoIlGhiEU/1HPMen0UUIs0LUu1ywpjCex2yTWnU2YmEwy0MQI1sekSr96QFxDDz9JcynYOYbqR/X9pdxEWyzQ+NJ3n6K97nE1Dj9Sgwu7mFGiUdNkf/SUAF0eZi/eXg71qumpMGBd4eWPtgkeMPLHjvMSYw9vBUfcoKFz6RJ4woG0dw5HOFkPnIjXKWllnl/o01EoBp/o8uswsIS9Nb8i+bp27U6tAHE',
diff --git a/lib/config/decrypt.ts b/lib/config/decrypt.ts
index 547f7b47e7..3d06a171f2 100644
--- a/lib/config/decrypt.ts
+++ b/lib/config/decrypt.ts
@@ -5,7 +5,7 @@ import { logger } from '../logger';
 import { maskToken } from '../util/mask';
 import { regEx } from '../util/regex';
 import { add } from '../util/sanitize';
-import { getGlobalConfig } from './global';
+import { GlobalConfig } from './global';
 import type { RenovateConfig } from './types';
 
 export async function tryDecryptPgp(
@@ -155,7 +155,7 @@ export async function decryptConfig(
 ): Promise<RenovateConfig> {
   logger.trace({ config }, 'decryptConfig()');
   const decryptedConfig = { ...config };
-  const { privateKey, privateKeyOld } = getGlobalConfig();
+  const { privateKey, privateKeyOld } = GlobalConfig.get();
   for (const [key, val] of Object.entries(config)) {
     if (key === 'encrypted' && is.object(val)) {
       logger.debug({ config: val }, 'Found encrypted config');
diff --git a/lib/config/global.ts b/lib/config/global.ts
index 0aefa514bd..313bc2ff4b 100644
--- a/lib/config/global.ts
+++ b/lib/config/global.ts
@@ -1,40 +1,52 @@
 import type { RenovateConfig, RepoGlobalConfig } from './types';
 
-let repoGlobalConfig: RepoGlobalConfig = {};
+export class GlobalConfig {
+  // TODO: once global config work is complete, add a test to make sure this list includes all options with globalOnly=true (#9603)
+  private static readonly OPTIONS = [
+    'allowCustomCrateRegistries',
+    'allowedPostUpgradeCommands',
+    'allowPlugins',
+    'allowPostUpgradeCommandTemplating',
+    'allowScripts',
+    'binarySource',
+    'cacheDir',
+    'customEnvVariables',
+    'dockerChildPrefix',
+    'dockerImagePrefix',
+    'dockerUser',
+    'dryRun',
+    'exposeAllEnv',
+    'localDir',
+    'migratePresets',
+    'privateKey',
+    'privateKeyOld',
+  ];
 
-// TODO: once global config work is complete, add a test to make sure this list includes all options with globalOnly=true (#9603)
-const repoGlobalOptions = [
-  'allowCustomCrateRegistries',
-  'allowPlugins',
-  'allowPostUpgradeCommandTemplating',
-  'allowScripts',
-  'allowedPostUpgradeCommands',
-  'binarySource',
-  'customEnvVariables',
-  'dockerChildPrefix',
-  'dockerImagePrefix',
-  'dockerUser',
-  'dryRun',
-  'exposeAllEnv',
-  'migratePresets',
-  'privateKey',
-  'privateKeyOld',
-  'localDir',
-  'cacheDir',
-];
+  private static config: RepoGlobalConfig = {};
 
-export function setGlobalConfig(
-  config: RenovateConfig | RepoGlobalConfig = {}
-): RenovateConfig {
-  repoGlobalConfig = {};
-  const result = { ...config };
-  for (const option of repoGlobalOptions) {
-    repoGlobalConfig[option] = config[option];
-    delete result[option];
+  static get(): RepoGlobalConfig;
+  static get<Key extends keyof RepoGlobalConfig>(
+    key?: Key
+  ): RepoGlobalConfig[Key];
+  static get<Key extends keyof RepoGlobalConfig>(
+    key?: Key
+  ): RepoGlobalConfig | RepoGlobalConfig[Key] {
+    return key ? GlobalConfig.config[key] : GlobalConfig.config;
   }
-  return result;
-}
 
-export function getGlobalConfig(): RepoGlobalConfig {
-  return repoGlobalConfig;
+  static set(config: RenovateConfig | RepoGlobalConfig): RenovateConfig {
+    GlobalConfig.reset();
+
+    const result = { ...config };
+    for (const option of GlobalConfig.OPTIONS) {
+      GlobalConfig.config[option] = config[option];
+      delete result[option];
+    }
+
+    return result;
+  }
+
+  static reset(): void {
+    GlobalConfig.config = {};
+  }
 }
diff --git a/lib/config/migration.spec.ts b/lib/config/migration.spec.ts
index 9f2b54634d..8f4a4202ac 100644
--- a/lib/config/migration.spec.ts
+++ b/lib/config/migration.spec.ts
@@ -1,6 +1,6 @@
 import { PlatformId } from '../constants';
 import { getConfig } from './defaults';
-import { setGlobalConfig } from './global';
+import { GlobalConfig } from './global';
 import * as configMigration from './migration';
 import type {
   MigratedConfig,
@@ -703,7 +703,7 @@ describe('config/migration', () => {
     });
   });
   it('it migrates presets', () => {
-    setGlobalConfig({
+    GlobalConfig.set({
       migratePresets: {
         '@org': 'local>org/renovate-config',
         '@org2/foo': '',
diff --git a/lib/config/migration.ts b/lib/config/migration.ts
index 2d7fd47f07..4219e35676 100644
--- a/lib/config/migration.ts
+++ b/lib/config/migration.ts
@@ -4,7 +4,7 @@ import { dequal } from 'dequal';
 import { logger } from '../logger';
 import { clone } from '../util/clone';
 import { regEx } from '../util/regex';
-import { getGlobalConfig } from './global';
+import { GlobalConfig } from './global';
 import { MigrationsService } from './migrations';
 import { getOptions } from './options';
 import { removedPresets } from './presets/common';
@@ -42,7 +42,7 @@ export function migrateConfig(
       'optionalDependencies',
       'peerDependencies',
     ];
-    const { migratePresets } = getGlobalConfig();
+    const { migratePresets } = GlobalConfig.get();
     for (const [key, val] of Object.entries(config)) {
       if (key === 'pathRules') {
         if (is.array(val)) {
diff --git a/lib/config/presets/npm/index.spec.ts b/lib/config/presets/npm/index.spec.ts
index 9ec951dd42..3c55df68bb 100644
--- a/lib/config/presets/npm/index.spec.ts
+++ b/lib/config/presets/npm/index.spec.ts
@@ -1,5 +1,5 @@
 import * as httpMock from '../../../../test/http-mock';
-import { setGlobalConfig } from '../../global';
+import { GlobalConfig } from '../../global';
 import * as npm from '.';
 
 jest.mock('registry-auth-token');
@@ -8,7 +8,7 @@ jest.mock('delay');
 describe('config/presets/npm/index', () => {
   beforeEach(() => {
     jest.resetAllMocks();
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
   afterEach(() => {
     delete process.env.RENOVATE_CACHE_NPM_MINUTES;
diff --git a/lib/datasource/crate/index.spec.ts b/lib/datasource/crate/index.spec.ts
index 1e6a0f9b59..37fbfdde95 100644
--- a/lib/datasource/crate/index.spec.ts
+++ b/lib/datasource/crate/index.spec.ts
@@ -6,7 +6,7 @@ import { dirname, join } from 'upath';
 import { getPkgReleases } from '..';
 import * as httpMock from '../../../test/http-mock';
 import { loadFixture } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import * as memCache from '../../util/cache/memory';
 import { RegistryFlavor, RegistryInfo } from './types';
@@ -102,7 +102,7 @@ describe('datasource/crate/index', () => {
         localDir: join(tmpDir.path, 'local'),
         cacheDir: join(tmpDir.path, 'cache'),
       };
-      setGlobalConfig(adminConfig);
+      GlobalConfig.set(adminConfig);
 
       simpleGit.mockReset();
       memCache.init();
@@ -111,7 +111,7 @@ describe('datasource/crate/index', () => {
     afterEach(async () => {
       await tmpDir.cleanup();
       tmpDir = null;
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
 
     it('returns null for missing registry url', async () => {
@@ -247,7 +247,7 @@ describe('datasource/crate/index', () => {
     });
     it('clones cloudsmith private registry', async () => {
       const { mockClone } = setupGitMocks();
-      setGlobalConfig({ ...adminConfig, allowCustomCrateRegistries: true });
+      GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true });
       const url = 'https://dl.cloudsmith.io/basic/myorg/myrepo/cargo/index.git';
       const res = await getPkgReleases({
         datasource,
@@ -261,7 +261,7 @@ describe('datasource/crate/index', () => {
     });
     it('clones other private registry', async () => {
       const { mockClone } = setupGitMocks();
-      setGlobalConfig({ ...adminConfig, allowCustomCrateRegistries: true });
+      GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true });
       const url = 'https://github.com/mcorbin/testregistry';
       const res = await getPkgReleases({
         datasource,
@@ -275,7 +275,7 @@ describe('datasource/crate/index', () => {
     });
     it('clones once then reuses the cache', async () => {
       const { mockClone } = setupGitMocks();
-      setGlobalConfig({ ...adminConfig, allowCustomCrateRegistries: true });
+      GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true });
       const url = 'https://github.com/mcorbin/othertestregistry';
       await getPkgReleases({
         datasource,
@@ -291,7 +291,7 @@ describe('datasource/crate/index', () => {
     });
     it('guards against race conditions while cloning', async () => {
       const { mockClone } = setupGitMocks(250);
-      setGlobalConfig({ ...adminConfig, allowCustomCrateRegistries: true });
+      GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true });
       const url = 'https://github.com/mcorbin/othertestregistry';
 
       await Promise.all([
@@ -317,7 +317,7 @@ describe('datasource/crate/index', () => {
     });
     it('returns null when git clone fails', async () => {
       setupErrorGitMock();
-      setGlobalConfig({ ...adminConfig, allowCustomCrateRegistries: true });
+      GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true });
       const url = 'https://github.com/mcorbin/othertestregistry';
 
       const result = await getPkgReleases({
diff --git a/lib/datasource/crate/index.ts b/lib/datasource/crate/index.ts
index ef92d12700..b7c69137b9 100644
--- a/lib/datasource/crate/index.ts
+++ b/lib/datasource/crate/index.ts
@@ -1,7 +1,7 @@
 import hasha from 'hasha';
 import Git from 'simple-git';
 import { join } from 'upath';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { logger } from '../../logger';
 import * as memCache from '../../util/cache/memory';
 import { cache } from '../../util/cache/package/decorator';
@@ -186,7 +186,7 @@ export class CrateDatasource extends Datasource {
     };
 
     if (flavor !== RegistryFlavor.CratesIo) {
-      if (!getGlobalConfig().allowCustomCrateRegistries) {
+      if (!GlobalConfig.get('allowCustomCrateRegistries')) {
         logger.warn(
           'crate datasource: allowCustomCrateRegistries=true is required for registries other than crates.io, bailing out'
         );
diff --git a/lib/datasource/npm/index.spec.ts b/lib/datasource/npm/index.spec.ts
index 21d15880aa..ed891df75a 100644
--- a/lib/datasource/npm/index.spec.ts
+++ b/lib/datasource/npm/index.spec.ts
@@ -2,7 +2,7 @@ import mockDate from 'mockdate';
 import _registryAuthToken from 'registry-auth-token';
 import { getPkgReleases } from '..';
 import * as httpMock from '../../../test/http-mock';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages';
 import * as hostRules from '../../util/host-rules';
 import { id as datasource, getNpmrc, resetCache, setNpmrc } from '.';
@@ -17,7 +17,7 @@ let npmResponse: any;
 describe('datasource/npm/index', () => {
   beforeEach(() => {
     jest.resetAllMocks();
-    setGlobalConfig();
+    GlobalConfig.reset();
     hostRules.clear();
     resetCache();
     setNpmrc();
@@ -357,7 +357,7 @@ describe('datasource/npm/index', () => {
       .reply(200, npmResponse);
     process.env.REGISTRY = 'https://registry.from-env.com';
     process.env.RENOVATE_CACHE_NPM_MINUTES = '15';
-    setGlobalConfig({ exposeAllEnv: true });
+    GlobalConfig.set({ exposeAllEnv: true });
 
     const npmrc = 'registry=${REGISTRY}';
     const res = await getPkgReleases({ datasource, depName: 'foobar', npmrc });
@@ -366,7 +366,7 @@ describe('datasource/npm/index', () => {
   });
 
   it('should throw error if necessary env var is not present', () => {
-    setGlobalConfig({ exposeAllEnv: true });
+    GlobalConfig.set({ exposeAllEnv: true });
 
     expect(() => setNpmrc('registry=${REGISTRY_MISSING}')).toThrow(
       Error('env-replace')
diff --git a/lib/datasource/npm/npmrc.spec.ts b/lib/datasource/npm/npmrc.spec.ts
index 1bd3e86e84..07a6a7faa1 100644
--- a/lib/datasource/npm/npmrc.spec.ts
+++ b/lib/datasource/npm/npmrc.spec.ts
@@ -1,5 +1,5 @@
 import { mocked } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import * as _sanitize from '../../util/sanitize';
 import { getNpmrc, setNpmrc } from './npmrc';
 
@@ -10,7 +10,7 @@ const sanitize = mocked(_sanitize);
 describe('datasource/npm/npmrc', () => {
   beforeEach(() => {
     setNpmrc('');
-    setGlobalConfig();
+    GlobalConfig.reset();
     jest.resetAllMocks();
   });
 
@@ -37,7 +37,7 @@ describe('datasource/npm/npmrc', () => {
   });
 
   it('sanitize _authtoken with high trust', () => {
-    setGlobalConfig({ exposeAllEnv: true });
+    GlobalConfig.set({ exposeAllEnv: true });
     process.env.TEST_TOKEN = 'test';
     setNpmrc(
       '//registry.test.com:_authToken=${TEST_TOKEN}\n_authToken=\nregistry=http://localhost'
diff --git a/lib/datasource/npm/npmrc.ts b/lib/datasource/npm/npmrc.ts
index 7ca1d60581..8a927ef636 100644
--- a/lib/datasource/npm/npmrc.ts
+++ b/lib/datasource/npm/npmrc.ts
@@ -3,7 +3,7 @@ import is from '@sindresorhus/is';
 import ini from 'ini';
 import registryAuthToken from 'registry-auth-token';
 import getRegistryUrl from 'registry-auth-token/registry-url';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { logger } from '../../logger';
 import type { OutgoingHttpHeaders } from '../../util/http/types';
 import { maskToken } from '../../util/mask';
@@ -61,7 +61,7 @@ export function setNpmrc(input?: string): void {
     npmrcRaw = input;
     logger.debug('Setting npmrc');
     npmrc = ini.parse(input.replace(regEx(/\\n/g), '\n'));
-    const { exposeAllEnv } = getGlobalConfig();
+    const { exposeAllEnv } = GlobalConfig.get();
     for (const [key, val] of Object.entries(npmrc)) {
       if (!exposeAllEnv) {
         sanitize(key, val);
diff --git a/lib/manager/batect/extract.spec.ts b/lib/manager/batect/extract.spec.ts
index 8b76e27da3..dcda4ee572 100644
--- a/lib/manager/batect/extract.spec.ts
+++ b/lib/manager/batect/extract.spec.ts
@@ -1,4 +1,4 @@
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import { GitTagsDatasource } from '../../datasource/git-tags';
 import { id as dockerVersioning } from '../../versioning/docker';
@@ -35,11 +35,11 @@ const config: ExtractConfig = {};
 describe('manager/batect/extract', () => {
   describe('extractPackageFile()', () => {
     beforeEach(() => {
-      setGlobalConfig(adminConfig);
+      GlobalConfig.set(adminConfig);
     });
 
     afterEach(() => {
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
 
     it('returns empty array for empty configuration file', async () => {
diff --git a/lib/manager/bundler/artifacts.spec.ts b/lib/manager/bundler/artifacts.spec.ts
index a13202422d..deca0c00d3 100644
--- a/lib/manager/bundler/artifacts.spec.ts
+++ b/lib/manager/bundler/artifacts.spec.ts
@@ -2,7 +2,7 @@ import { exec as _exec } from 'child_process';
 import { join } from 'upath';
 import { envMock, mockExecAll } from '../../../test/exec-util';
 import { fs, git, mocked } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import * as _datasource from '../../datasource';
 import * as docker from '../../util/exec/docker';
@@ -52,11 +52,11 @@ describe('manager/bundler/artifacts', () => {
     bundlerHostRules.findAllAuthenticatable.mockReturnValue([]);
     docker.resetPrefetchedImages();
 
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
     fs.ensureCacheDir.mockResolvedValue('/tmp/cache/others/gem');
   });
   afterEach(() => {
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
   it('returns null by default', async () => {
     expect(
@@ -106,7 +106,7 @@ describe('manager/bundler/artifacts', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('works explicit global binarySource', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'global' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'global' });
     fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock');
     fs.writeLocalFile.mockResolvedValueOnce(null as never);
     fs.readLocalFile.mockResolvedValueOnce(null);
@@ -127,7 +127,7 @@ describe('manager/bundler/artifacts', () => {
   });
   describe('Docker', () => {
     beforeEach(() => {
-      setGlobalConfig({
+      GlobalConfig.set({
         ...adminConfig,
         binarySource: 'docker',
       });
@@ -159,7 +159,7 @@ describe('manager/bundler/artifacts', () => {
       expect(execSnapshots).toMatchSnapshot();
     });
     it('constraints options', async () => {
-      setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+      GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
       fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock');
       fs.writeLocalFile.mockResolvedValueOnce(null as never);
       datasource.getPkgReleases.mockResolvedValueOnce({
@@ -191,7 +191,7 @@ describe('manager/bundler/artifacts', () => {
       expect(execSnapshots).toMatchSnapshot();
     });
     it('invalid constraints options', async () => {
-      setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+      GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
       fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock');
       fs.writeLocalFile.mockResolvedValueOnce(null as never);
       datasource.getPkgReleases.mockResolvedValueOnce({
@@ -224,7 +224,7 @@ describe('manager/bundler/artifacts', () => {
     });
 
     it('injects bundler host configuration environment variables', async () => {
-      setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+      GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
       fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock');
       fs.writeLocalFile.mockResolvedValueOnce(null as never);
       fs.readLocalFile.mockResolvedValueOnce('1.2.0');
@@ -264,7 +264,7 @@ describe('manager/bundler/artifacts', () => {
     });
 
     it('injects bundler host configuration as command with bundler < 2', async () => {
-      setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+      GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
       fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock');
       fs.writeLocalFile.mockResolvedValueOnce(null as never);
       fs.readLocalFile.mockResolvedValueOnce('1.2.0');
@@ -309,7 +309,7 @@ describe('manager/bundler/artifacts', () => {
     });
 
     it('injects bundler host configuration as command with bundler >= 2', async () => {
-      setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+      GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
       fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock');
       fs.writeLocalFile.mockResolvedValueOnce(null as never);
       fs.readLocalFile.mockResolvedValueOnce('1.2.0');
@@ -354,7 +354,7 @@ describe('manager/bundler/artifacts', () => {
     });
 
     it('injects bundler host configuration as command with bundler == latest', async () => {
-      setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+      GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
       fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock');
       fs.writeLocalFile.mockResolvedValueOnce(null as never);
       fs.readLocalFile.mockResolvedValueOnce('1.2.0');
diff --git a/lib/manager/cargo/artifacts.spec.ts b/lib/manager/cargo/artifacts.spec.ts
index b3ee807645..aa867d7a87 100644
--- a/lib/manager/cargo/artifacts.spec.ts
+++ b/lib/manager/cargo/artifacts.spec.ts
@@ -3,7 +3,7 @@ import _fs from 'fs-extra';
 import { join } from 'upath';
 import { envMock, mockExecAll } from '../../../test/exec-util';
 import { git, mocked } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import * as docker from '../../util/exec/docker';
 import * as _env from '../../util/exec/env';
@@ -33,11 +33,11 @@ describe('manager/cargo/artifacts', () => {
     jest.resetModules();
 
     env.getChildProcessEnv.mockReturnValue(envMock.basic);
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
     docker.resetPrefetchedImages();
   });
   afterEach(() => {
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
   it('returns null if no Cargo.lock found', async () => {
     fs.stat.mockRejectedValue(new Error('not found!'));
@@ -171,7 +171,7 @@ describe('manager/cargo/artifacts', () => {
 
   it('returns updated Cargo.lock with docker', async () => {
     fs.stat.mockResolvedValueOnce({ name: 'Cargo.lock' } as any);
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     git.getFile.mockResolvedValueOnce('Old Cargo.lock');
     const execSnapshots = mockExecAll(exec);
     fs.readFile.mockResolvedValueOnce('New Cargo.lock' as any);
diff --git a/lib/manager/cargo/extract.spec.ts b/lib/manager/cargo/extract.spec.ts
index d8c3361285..9fa7553744 100644
--- a/lib/manager/cargo/extract.spec.ts
+++ b/lib/manager/cargo/extract.spec.ts
@@ -1,7 +1,7 @@
 import { DirectoryResult, dir } from 'tmp-promise';
 import { join } from 'upath';
 import { loadFixture } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import { writeLocalFile } from '../../util/fs';
 import type { ExtractConfig } from '../types';
@@ -29,11 +29,11 @@ describe('manager/cargo/extract', () => {
         cacheDir: join(tmpDir.path, 'cache'),
       };
 
-      setGlobalConfig(adminConfig);
+      GlobalConfig.set(adminConfig);
     });
     afterEach(async () => {
       await tmpDir.cleanup();
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
     it('returns null for invalid toml', async () => {
       expect(
diff --git a/lib/manager/cocoapods/artifacts.spec.ts b/lib/manager/cocoapods/artifacts.spec.ts
index 06bd91f6af..f36e1c8fda 100644
--- a/lib/manager/cocoapods/artifacts.spec.ts
+++ b/lib/manager/cocoapods/artifacts.spec.ts
@@ -3,7 +3,7 @@ import _fs from 'fs-extra';
 import { join } from 'upath';
 import { envMock, mockExecAll } from '../../../test/exec-util';
 import { git, mocked } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import * as _datasource from '../../datasource';
 import * as _env from '../../util/exec/env';
@@ -37,7 +37,7 @@ describe('manager/cocoapods/artifacts', () => {
     jest.resetAllMocks();
     env.getChildProcessEnv.mockReturnValue(envMock.basic);
 
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
 
     datasource.getPkgReleases.mockResolvedValue({
       releases: [
@@ -51,7 +51,7 @@ describe('manager/cocoapods/artifacts', () => {
     });
   });
   afterEach(() => {
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
   it('returns null if no Podfile.lock found', async () => {
     const execSnapshots = mockExecAll(exec);
@@ -79,7 +79,7 @@ describe('manager/cocoapods/artifacts', () => {
   });
   it('returns null for invalid local directory', async () => {
     const execSnapshots = mockExecAll(exec);
-    setGlobalConfig({
+    GlobalConfig.set({
       localDir: '',
     });
 
@@ -124,7 +124,7 @@ describe('manager/cocoapods/artifacts', () => {
   });
   it('returns updated Podfile', async () => {
     const execSnapshots = mockExecAll(exec);
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     fs.readFile.mockResolvedValueOnce('Old Podfile' as any);
     git.getRepoStatus.mockResolvedValueOnce({
       modified: ['Podfile.lock'],
@@ -142,7 +142,7 @@ describe('manager/cocoapods/artifacts', () => {
   });
   it('returns updated Podfile and Pods files', async () => {
     const execSnapshots = mockExecAll(exec);
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     fs.readFile.mockResolvedValueOnce('Old Manifest.lock' as any);
     fs.readFile.mockResolvedValueOnce('New Podfile' as any);
     fs.readFile.mockResolvedValueOnce('Pods manifest' as any);
@@ -204,7 +204,7 @@ describe('manager/cocoapods/artifacts', () => {
   it('dynamically selects Docker image tag', async () => {
     const execSnapshots = mockExecAll(exec);
 
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
 
     fs.readFile.mockResolvedValueOnce('COCOAPODS: 1.2.4' as any);
 
@@ -229,7 +229,7 @@ describe('manager/cocoapods/artifacts', () => {
   it('falls back to the `latest` Docker image tag', async () => {
     const execSnapshots = mockExecAll(exec);
 
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
 
     fs.readFile.mockResolvedValueOnce('COCOAPODS: 1.2.4' as any);
     datasource.getPkgReleases.mockResolvedValueOnce({
diff --git a/lib/manager/cocoapods/extract.spec.ts b/lib/manager/cocoapods/extract.spec.ts
index 3ff03c6367..b0e1a311d2 100644
--- a/lib/manager/cocoapods/extract.spec.ts
+++ b/lib/manager/cocoapods/extract.spec.ts
@@ -1,5 +1,5 @@
 import { loadFixture } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import { extractPackageFile } from '.';
 
@@ -11,7 +11,7 @@ const adminConfig: RepoGlobalConfig = { localDir: '' };
 describe('manager/cocoapods/extract', () => {
   describe('extractPackageFile()', () => {
     it('extracts from simple file', async () => {
-      setGlobalConfig(adminConfig);
+      GlobalConfig.set(adminConfig);
       const { deps } = await extractPackageFile(simplePodfile, 'Podfile');
       expect(deps).toMatchSnapshot([
         { depName: 'a' },
@@ -40,7 +40,7 @@ describe('manager/cocoapods/extract', () => {
     });
 
     it('extracts from complex file', async () => {
-      setGlobalConfig(adminConfig);
+      GlobalConfig.set(adminConfig);
       const { deps } = await extractPackageFile(complexPodfile, 'Podfile');
       expect(deps).toMatchSnapshot([
         { depName: 'IQKeyboardManager', currentValue: '~> 6.5.0' },
diff --git a/lib/manager/composer/artifacts.spec.ts b/lib/manager/composer/artifacts.spec.ts
index a0cdc01deb..3373630d4b 100644
--- a/lib/manager/composer/artifacts.spec.ts
+++ b/lib/manager/composer/artifacts.spec.ts
@@ -1,7 +1,7 @@
 import { join } from 'upath';
 import { envMock, exec, mockExecAll } from '../../../test/exec-util';
 import { env, fs, git, mocked, partial } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import { PlatformId } from '../../constants';
 import * as _datasource from '../../datasource';
@@ -46,7 +46,7 @@ describe('manager/composer/artifacts', () => {
     env.getChildProcessEnv.mockReturnValue(envMock.basic);
     docker.resetPrefetchedImages();
     hostRules.clear();
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
     fs.ensureCacheDir.mockResolvedValue('/tmp/renovate/cache/others/composer');
     datasource.getPkgReleases.mockResolvedValueOnce({
       releases: [
@@ -62,7 +62,7 @@ describe('manager/composer/artifacts', () => {
   });
 
   afterEach(() => {
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
 
   it('returns if no composer.lock found', async () => {
@@ -81,7 +81,11 @@ describe('manager/composer/artifacts', () => {
     const execSnapshots = mockExecAll(exec);
     fs.readLocalFile.mockResolvedValueOnce('{}');
     git.getRepoStatus.mockResolvedValueOnce(repoStatus);
-    setGlobalConfig({ ...adminConfig, allowScripts: true, allowPlugins: true });
+    GlobalConfig.set({
+      ...adminConfig,
+      allowScripts: true,
+      allowPlugins: true,
+    });
     expect(
       await composer.updateArtifacts({
         packageFileName: 'composer.json',
@@ -220,7 +224,7 @@ describe('manager/composer/artifacts', () => {
   });
 
   it('supports docker mode', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     fs.readLocalFile.mockResolvedValueOnce('{}');
 
     const execSnapshots = mockExecAll(exec);
@@ -255,7 +259,7 @@ describe('manager/composer/artifacts', () => {
   });
 
   it('supports global mode', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'global' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'global' });
     fs.readLocalFile.mockResolvedValueOnce('{}');
     const execSnapshots = mockExecAll(exec);
     fs.readLocalFile.mockResolvedValueOnce('{ }');
@@ -422,7 +426,7 @@ describe('manager/composer/artifacts', () => {
     const execSnapshots = mockExecAll(exec);
     fs.readLocalFile.mockResolvedValueOnce('{}');
     git.getRepoStatus.mockResolvedValueOnce(repoStatus);
-    setGlobalConfig({ ...adminConfig, allowPlugins: true });
+    GlobalConfig.set({ ...adminConfig, allowPlugins: true });
     expect(
       await composer.updateArtifacts({
         packageFileName: 'composer.json',
@@ -439,7 +443,7 @@ describe('manager/composer/artifacts', () => {
     const execSnapshots = mockExecAll(exec);
     fs.readLocalFile.mockResolvedValueOnce('{}');
     git.getRepoStatus.mockResolvedValueOnce(repoStatus);
-    setGlobalConfig({ ...adminConfig, allowPlugins: true });
+    GlobalConfig.set({ ...adminConfig, allowPlugins: true });
     expect(
       await composer.updateArtifacts({
         packageFileName: 'composer.json',
diff --git a/lib/manager/composer/utils.spec.ts b/lib/manager/composer/utils.spec.ts
index 7213bf1333..5553063184 100644
--- a/lib/manager/composer/utils.spec.ts
+++ b/lib/manager/composer/utils.spec.ts
@@ -1,4 +1,4 @@
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import {
   extractContraints,
   getComposerArguments,
@@ -46,7 +46,7 @@ describe('manager/composer/utils', () => {
 
   describe('getComposerArguments', () => {
     afterEach(() => {
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
 
     it('disables scripts and plugins by default', () => {
@@ -82,7 +82,7 @@ describe('manager/composer/utils', () => {
       );
     });
     it('allows scripts when configured', () => {
-      setGlobalConfig({
+      GlobalConfig.set({
         allowScripts: true,
       });
       expect(getComposerArguments({})).toBe(
@@ -90,7 +90,7 @@ describe('manager/composer/utils', () => {
       );
     });
     it('disables scripts when configured locally', () => {
-      setGlobalConfig({
+      GlobalConfig.set({
         allowScripts: true,
       });
       expect(
@@ -102,7 +102,7 @@ describe('manager/composer/utils', () => {
       );
     });
     it('allows plugins when configured', () => {
-      setGlobalConfig({
+      GlobalConfig.set({
         allowPlugins: true,
       });
       expect(getComposerArguments({})).toBe(
@@ -110,7 +110,7 @@ describe('manager/composer/utils', () => {
       );
     });
     it('disables plugins when configured locally', () => {
-      setGlobalConfig({
+      GlobalConfig.set({
         allowPlugins: true,
       });
       expect(
diff --git a/lib/manager/composer/utils.ts b/lib/manager/composer/utils.ts
index d88609f129..5b6b3a2c00 100644
--- a/lib/manager/composer/utils.ts
+++ b/lib/manager/composer/utils.ts
@@ -1,5 +1,5 @@
 import { quote } from 'shlex';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { logger } from '../../logger';
 import { api, id as composerVersioningId } from '../../versioning/composer';
 import type { UpdateArtifactsConfig } from '../types';
@@ -23,11 +23,11 @@ export function getComposerArguments(config: UpdateArtifactsConfig): string {
   }
 
   args += ' --no-ansi --no-interaction';
-  if (!getGlobalConfig().allowScripts || config.ignoreScripts) {
+  if (!GlobalConfig.get('allowScripts') || config.ignoreScripts) {
     args += ' --no-scripts --no-autoloader';
   }
 
-  if (!getGlobalConfig().allowPlugins || config.ignorePlugins) {
+  if (!GlobalConfig.get('allowPlugins') || config.ignorePlugins) {
     args += ' --no-plugins';
   }
 
diff --git a/lib/manager/git-submodules/extract.spec.ts b/lib/manager/git-submodules/extract.spec.ts
index 1b5ba09828..9fe186ed5e 100644
--- a/lib/manager/git-submodules/extract.spec.ts
+++ b/lib/manager/git-submodules/extract.spec.ts
@@ -1,7 +1,7 @@
 import { mock } from 'jest-mock-extended';
 import _simpleGit, { Response, SimpleGit } from 'simple-git';
 import { partial } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import * as hostRules from '../../util/host-rules';
 import type { PackageFile } from '../types';
 import extractPackageFile from './extract';
@@ -44,7 +44,7 @@ describe('manager/git-submodules/extract', () => {
   });
   describe('extractPackageFile()', () => {
     it('extracts submodules', async () => {
-      setGlobalConfig({ localDir: `${__dirname}/__fixtures__` });
+      GlobalConfig.set({ localDir: `${__dirname}/__fixtures__` });
       hostRules.add({ matchHost: 'github.com', token: '123test' });
       let res: PackageFile;
       expect(await extractPackageFile('', '.gitmodules.1', {})).toBeNull();
diff --git a/lib/manager/git-submodules/extract.ts b/lib/manager/git-submodules/extract.ts
index 0017b70e8d..f67c0b46a7 100644
--- a/lib/manager/git-submodules/extract.ts
+++ b/lib/manager/git-submodules/extract.ts
@@ -1,7 +1,7 @@
 import URL from 'url';
 import Git, { SimpleGit } from 'simple-git';
 import upath from 'upath';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { GitRefsDatasource } from '../../datasource/git-refs';
 import { logger } from '../../logger';
 import { simpleGitConfig } from '../../util/git/config';
@@ -90,7 +90,7 @@ export default async function extractPackageFile(
   fileName: string,
   config: ExtractConfig
 ): Promise<PackageFile | null> {
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   const git = Git(localDir);
   const gitModulesPath = upath.join(localDir, fileName);
 
diff --git a/lib/manager/git-submodules/update.spec.ts b/lib/manager/git-submodules/update.spec.ts
index 358f066713..0e95061ea0 100644
--- a/lib/manager/git-submodules/update.spec.ts
+++ b/lib/manager/git-submodules/update.spec.ts
@@ -1,7 +1,7 @@
 import _simpleGit from 'simple-git';
 import { DirectoryResult, dir } from 'tmp-promise';
 import { join } from 'upath';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import type { Upgrade } from '../types';
 import updateDependency from './update';
@@ -19,11 +19,11 @@ describe('manager/git-submodules/update', () => {
 
       tmpDir = await dir({ unsafeCleanup: true });
       adminConfig = { localDir: join(tmpDir.path) };
-      setGlobalConfig(adminConfig);
+      GlobalConfig.set(adminConfig);
     });
     afterAll(async () => {
       await tmpDir.cleanup();
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
     it('returns null on error', async () => {
       simpleGit.mockReturnValue({
diff --git a/lib/manager/git-submodules/update.ts b/lib/manager/git-submodules/update.ts
index 314fa05aeb..ee4c432e57 100644
--- a/lib/manager/git-submodules/update.ts
+++ b/lib/manager/git-submodules/update.ts
@@ -1,6 +1,6 @@
 import Git from 'simple-git';
 import upath from 'upath';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { logger } from '../../logger';
 import type { UpdateDependencyConfig } from '../types';
 
@@ -8,7 +8,7 @@ export default async function updateDependency({
   fileContent,
   upgrade,
 }: UpdateDependencyConfig): Promise<string | null> {
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   const git = Git(localDir);
   const submoduleGit = Git(upath.join(localDir, upgrade.depName));
 
diff --git a/lib/manager/gitlabci/extract.spec.ts b/lib/manager/gitlabci/extract.spec.ts
index 04c6ec9225..ca18521d5c 100644
--- a/lib/manager/gitlabci/extract.spec.ts
+++ b/lib/manager/gitlabci/extract.spec.ts
@@ -1,5 +1,5 @@
 import { logger } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import type { ExtractConfig, PackageDependency } from '../types';
 import { extractAllPackageFiles } from './extract';
@@ -10,11 +10,11 @@ const adminConfig: RepoGlobalConfig = { localDir: '' };
 
 describe('manager/gitlabci/extract', () => {
   beforeEach(() => {
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
   });
 
   afterEach(() => {
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
 
   describe('extractAllPackageFiles()', () => {
diff --git a/lib/manager/gomod/artifacts.spec.ts b/lib/manager/gomod/artifacts.spec.ts
index 482842efc8..81520b3ac7 100644
--- a/lib/manager/gomod/artifacts.spec.ts
+++ b/lib/manager/gomod/artifacts.spec.ts
@@ -3,7 +3,7 @@ import _fs from 'fs-extra';
 import { join } from 'upath';
 import { envMock, mockExecAll } from '../../../test/exec-util';
 import { git, mocked } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import * as docker from '../../util/exec/docker';
 import * as _env from '../../util/exec/env';
@@ -61,11 +61,11 @@ describe('manager/gomod/artifacts', () => {
 
     delete process.env.GOPATH;
     env.getChildProcessEnv.mockReturnValue({ ...envMock.basic, ...goEnv });
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
     docker.resetPrefetchedImages();
   });
   afterEach(() => {
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
   it('returns if no go.sum found', async () => {
     const execSnapshots = mockExecAll(exec);
@@ -151,7 +151,7 @@ describe('manager/gomod/artifacts', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('supports docker mode without credentials', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     fs.readFile.mockResolvedValueOnce('Current go.sum' as any);
     fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename
     const execSnapshots = mockExecAll(exec);
@@ -170,7 +170,7 @@ describe('manager/gomod/artifacts', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('supports global mode', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'global' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'global' });
     fs.readFile.mockResolvedValueOnce('Current go.sum' as any);
     fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename
     const execSnapshots = mockExecAll(exec);
@@ -189,7 +189,7 @@ describe('manager/gomod/artifacts', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('supports docker mode with credentials', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     hostRules.find.mockReturnValueOnce({
       token: 'some-token',
     });
@@ -212,7 +212,7 @@ describe('manager/gomod/artifacts', () => {
   });
 
   it('supports docker mode with 2 credentials', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     hostRules.find.mockReturnValueOnce({
       token: 'some-token',
     });
@@ -256,7 +256,7 @@ describe('manager/gomod/artifacts', () => {
   });
 
   it('supports docker mode with single credential', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     hostRules.getAll.mockReturnValueOnce([
       {
         token: 'some-enterprise-token',
@@ -295,7 +295,7 @@ describe('manager/gomod/artifacts', () => {
   });
 
   it('supports docker mode with multiple credentials for different paths', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     hostRules.getAll.mockReturnValueOnce([
       {
         token: 'some-enterprise-token-repo1',
@@ -341,7 +341,7 @@ describe('manager/gomod/artifacts', () => {
   });
 
   it('supports docker mode and ignores non http credentials', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     hostRules.getAll.mockReturnValueOnce([
       {
         token: 'some-token',
@@ -384,7 +384,7 @@ describe('manager/gomod/artifacts', () => {
   });
 
   it('supports docker mode with many credentials', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     hostRules.find.mockReturnValueOnce({
       token: 'some-token',
     });
@@ -442,7 +442,7 @@ describe('manager/gomod/artifacts', () => {
   });
 
   it('supports docker mode and ignores non git credentials', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     hostRules.find.mockReturnValueOnce({
       token: 'some-token',
     });
@@ -484,7 +484,7 @@ describe('manager/gomod/artifacts', () => {
   });
 
   it('supports docker mode with goModTidy', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     hostRules.find.mockReturnValueOnce({});
     fs.readFile.mockResolvedValueOnce('Current go.sum' as any);
     fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename
diff --git a/lib/manager/gomod/artifacts.ts b/lib/manager/gomod/artifacts.ts
index 14c81c7341..54c9043748 100644
--- a/lib/manager/gomod/artifacts.ts
+++ b/lib/manager/gomod/artifacts.ts
@@ -1,6 +1,6 @@
 import is from '@sindresorhus/is';
 import { dirname, join } from 'upath';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { PlatformId } from '../../constants';
 import { TEMPORARY_ERROR } from '../../constants/error-messages';
 import { logger } from '../../logger';
@@ -171,7 +171,7 @@ export async function updateArtifacts({
         GONOSUMDB: process.env.GONOSUMDB,
         GOSUMDB: process.env.GOSUMDB,
         GOFLAGS: useModcacherw(config.constraints?.go) ? '-modcacherw' : null,
-        CGO_ENABLED: getGlobalConfig().binarySource === 'docker' ? '0' : null,
+        CGO_ENABLED: GlobalConfig.get('binarySource') === 'docker' ? '0' : null,
         ...getGitEnvironmentVariables(),
       },
       docker: {
diff --git a/lib/manager/gradle-wrapper/artifacts-real.spec.ts b/lib/manager/gradle-wrapper/artifacts-real.spec.ts
index b8a406f062..4ae117f3d7 100644
--- a/lib/manager/gradle-wrapper/artifacts-real.spec.ts
+++ b/lib/manager/gradle-wrapper/artifacts-real.spec.ts
@@ -3,7 +3,7 @@ import Git from 'simple-git';
 import { resolve } from 'upath';
 import * as httpMock from '../../../test/http-mock';
 import { git, partial } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import type { StatusResult } from '../../util/git';
 import { ifSystemSupportsGradle } from '../gradle/deep/__testutil__/gradle';
@@ -42,12 +42,12 @@ describe('manager/gradle-wrapper/artifacts-real', () => {
 
     beforeEach(() => {
       jest.resetAllMocks();
-      setGlobalConfig(adminConfig);
+      GlobalConfig.set(adminConfig);
     });
 
     afterEach(async () => {
       await Git(fixtures).checkout(['HEAD', '--', '.']);
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
 
     it('replaces existing value', async () => {
@@ -168,7 +168,7 @@ describe('manager/gradle-wrapper/artifacts-real', () => {
         localDir: resolve(fixtures, './wrongCmd'),
       };
 
-      setGlobalConfig(wrongCmdConfig);
+      GlobalConfig.set(wrongCmdConfig);
       const res = await gradleWrapper.updateArtifacts({
         packageFileName: 'gradle/wrapper/gradle-wrapper.properties',
         updatedDeps: [],
@@ -191,7 +191,7 @@ describe('manager/gradle-wrapper/artifacts-real', () => {
     });
 
     it('gradlew not found', async () => {
-      setGlobalConfig({ localDir: 'some-dir' });
+      GlobalConfig.set({ localDir: 'some-dir' });
       const res = await gradleWrapper.updateArtifacts({
         packageFileName: 'gradle-wrapper.properties',
         updatedDeps: [],
diff --git a/lib/manager/gradle-wrapper/artifacts.spec.ts b/lib/manager/gradle-wrapper/artifacts.spec.ts
index a98cedc9d9..b7770644d8 100644
--- a/lib/manager/gradle-wrapper/artifacts.spec.ts
+++ b/lib/manager/gradle-wrapper/artifacts.spec.ts
@@ -9,7 +9,7 @@ import {
   git,
   partial,
 } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import { resetPrefetchedImages } from '../../util/exec/docker';
 import type { StatusResult } from '../../util/git';
@@ -50,7 +50,7 @@ describe('manager/gradle-wrapper/artifacts', () => {
       LC_ALL: 'en_US',
     });
 
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
     resetPrefetchedImages();
 
     fs.readLocalFile.mockResolvedValue('test');
@@ -58,7 +58,7 @@ describe('manager/gradle-wrapper/artifacts', () => {
   });
 
   afterEach(() => {
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
 
   it('replaces existing value', async () => {
@@ -97,7 +97,7 @@ describe('manager/gradle-wrapper/artifacts', () => {
   });
 
   it('gradlew not found', async () => {
-    setGlobalConfig({ ...adminConfig, localDir: 'some-dir' });
+    GlobalConfig.set({ ...adminConfig, localDir: 'some-dir' });
     const res = await gradleWrapper.updateArtifacts({
       packageFileName: 'gradle-wrapper.properties',
       updatedDeps: [],
diff --git a/lib/manager/gradle-wrapper/artifacts.ts b/lib/manager/gradle-wrapper/artifacts.ts
index ca14ee8325..98dabbe218 100644
--- a/lib/manager/gradle-wrapper/artifacts.ts
+++ b/lib/manager/gradle-wrapper/artifacts.ts
@@ -1,6 +1,6 @@
 import { quote } from 'shlex';
 import { resolve } from 'upath';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { TEMPORARY_ERROR } from '../../constants/error-messages';
 import { logger } from '../../logger';
 import { ExecOptions, exec } from '../../util/exec';
@@ -57,7 +57,7 @@ export async function updateArtifacts({
   config,
 }: UpdateArtifact): Promise<UpdateArtifactsResult[] | null> {
   try {
-    const { localDir: projectDir } = getGlobalConfig();
+    const projectDir = GlobalConfig.get('localDir');
     logger.debug({ updatedDeps }, 'gradle-wrapper.updateArtifacts()');
     const gradlew = gradleWrapperFileName();
     const gradlewPath = resolve(projectDir, `./${gradlew}`);
diff --git a/lib/manager/gradle-wrapper/util.spec.ts b/lib/manager/gradle-wrapper/util.spec.ts
index 61dd0852c7..e48174e5e4 100644
--- a/lib/manager/gradle-wrapper/util.spec.ts
+++ b/lib/manager/gradle-wrapper/util.spec.ts
@@ -1,4 +1,4 @@
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { extractGradleVersion, getJavaContraint } from './utils';
 
 describe('manager/gradle-wrapper/util', () => {
@@ -8,22 +8,22 @@ describe('manager/gradle-wrapper/util', () => {
     });
 
     it('return ^11.0.0 for docker mode and undefined gradle', () => {
-      setGlobalConfig({ binarySource: 'docker' });
+      GlobalConfig.set({ binarySource: 'docker' });
       expect(getJavaContraint(undefined)).toBe('^11.0.0');
     });
 
     it('return ^8.0.0 for docker gradle < 5', () => {
-      setGlobalConfig({ binarySource: 'docker' });
+      GlobalConfig.set({ binarySource: 'docker' });
       expect(getJavaContraint('4.9')).toBe('^8.0.0');
     });
 
     it('return ^11.0.0 for docker gradle >=5 && <7', () => {
-      setGlobalConfig({ binarySource: 'docker' });
+      GlobalConfig.set({ binarySource: 'docker' });
       expect(getJavaContraint('6.0')).toBe('^11.0.0');
     });
 
     it('return ^16.0.0 for docker gradle >= 7', () => {
-      setGlobalConfig({ binarySource: 'docker' });
+      GlobalConfig.set({ binarySource: 'docker' });
       expect(getJavaContraint('7.0.1')).toBe('^16.0.0');
     });
   });
diff --git a/lib/manager/gradle-wrapper/utils.ts b/lib/manager/gradle-wrapper/utils.ts
index 5dd3ebce55..99f389ed19 100644
--- a/lib/manager/gradle-wrapper/utils.ts
+++ b/lib/manager/gradle-wrapper/utils.ts
@@ -1,7 +1,7 @@
 import type { Stats } from 'fs';
 import os from 'os';
 import upath from 'upath';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { logger } from '../../logger';
 import { chmod } from '../../util/fs';
 import { regEx } from '../../util/regex';
@@ -17,7 +17,7 @@ export const extraEnv = {
 export function gradleWrapperFileName(): string {
   if (
     os.platform() === 'win32' &&
-    getGlobalConfig()?.binarySource !== 'docker'
+    GlobalConfig.get('binarySource') !== 'docker'
   ) {
     return 'gradlew.bat';
   }
@@ -53,7 +53,7 @@ export async function prepareGradleCommand(
  * @returns A Java semver range
  */
 export function getJavaContraint(gradleVersion: string): string | null {
-  if (getGlobalConfig()?.binarySource !== 'docker') {
+  if (GlobalConfig.get('binarySource') !== 'docker') {
     // ignore
     return null;
   }
diff --git a/lib/manager/gradle/deep/gradle-updates-report.spec.ts b/lib/manager/gradle/deep/gradle-updates-report.spec.ts
index fc804f22ca..25709740f8 100644
--- a/lib/manager/gradle/deep/gradle-updates-report.spec.ts
+++ b/lib/manager/gradle/deep/gradle-updates-report.spec.ts
@@ -1,7 +1,7 @@
 import * as fs from 'fs-extra';
 import tmp, { DirectoryResult } from 'tmp-promise';
 import * as upath from 'upath';
-import { setGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import { exec } from '../../../util/exec';
 import { extraEnv } from '../../gradle-wrapper/utils';
 import { ifSystemSupportsGradle } from './__testutil__/gradle';
@@ -22,7 +22,7 @@ describe('manager/gradle/deep/gradle-updates-report', () => {
 
         beforeEach(async () => {
           workingDir = await tmp.dir({ unsafeCleanup: true });
-          setGlobalConfig({ localDir: workingDir.path });
+          GlobalConfig.set({ localDir: workingDir.path });
         });
 
         afterEach(() => workingDir.cleanup());
diff --git a/lib/manager/gradle/deep/index-real.spec.ts b/lib/manager/gradle/deep/index-real.spec.ts
index d0bfcf894c..e6c0fbe321 100644
--- a/lib/manager/gradle/deep/index-real.spec.ts
+++ b/lib/manager/gradle/deep/index-real.spec.ts
@@ -1,6 +1,6 @@
 import fsExtra from 'fs-extra';
 import tmp, { DirectoryResult } from 'tmp-promise';
-import { setGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import type { RepoGlobalConfig } from '../../../config/types';
 import type { ExtractConfig } from '../../types';
 import { ifSystemSupportsGradle } from './__testutil__/gradle';
@@ -26,7 +26,7 @@ describe('manager/gradle/deep/index-real', () => {
       workingDir = await tmp.dir({ unsafeCleanup: true });
       successFile = '';
       adminConfig = { localDir: workingDir.path };
-      setGlobalConfig(adminConfig);
+      GlobalConfig.set(adminConfig);
       testRunConfig = { ...baseConfig };
       await fsExtra.copy(`${fixtures}/minimal-project`, workingDir.path);
       await fsExtra.copy(`${fixtures}/gradle-wrappers/6`, workingDir.path);
@@ -48,7 +48,7 @@ allprojects {
 
     afterEach(async () => {
       await workingDir.cleanup();
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
 
     it('executes an executable gradle wrapper', async () => {
diff --git a/lib/manager/gradle/deep/index.spec.ts b/lib/manager/gradle/deep/index.spec.ts
index ae15709771..9177bf06a1 100644
--- a/lib/manager/gradle/deep/index.spec.ts
+++ b/lib/manager/gradle/deep/index.spec.ts
@@ -9,7 +9,7 @@ import {
   fs,
   loadFixture,
 } from '../../../../test/util';
-import { setGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import type { RepoGlobalConfig } from '../../../config/types';
 import {
   ReleaseResult,
@@ -122,11 +122,11 @@ describe('manager/gradle/deep/index', () => {
   }
 
   beforeAll(() => {
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
   });
 
   afterAll(() => {
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
 
   beforeEach(() => {
@@ -304,7 +304,7 @@ describe('manager/gradle/deep/index', () => {
     });
 
     it('should use docker if required', async () => {
-      setGlobalConfig(dockerAdminConfig);
+      GlobalConfig.set(dockerAdminConfig);
       const execSnapshots = setupMocks({
         wrapperFilename: null,
         wrapperPropertiesFilename: null,
@@ -326,7 +326,7 @@ describe('manager/gradle/deep/index', () => {
     });
 
     it('should use docker even if gradlew is available', async () => {
-      setGlobalConfig(dockerAdminConfig);
+      GlobalConfig.set(dockerAdminConfig);
       const execSnapshots = setupMocks();
       getPkgReleases.mockResolvedValueOnce(javaReleases);
       const dependencies = await extractAllPackageFiles(config, [
@@ -345,7 +345,7 @@ describe('manager/gradle/deep/index', () => {
     });
 
     it('should use docker even if gradlew.bat is available on Windows', async () => {
-      setGlobalConfig(dockerAdminConfig);
+      GlobalConfig.set(dockerAdminConfig);
       jest.spyOn(os, 'platform').mockReturnValueOnce('win32');
       const execSnapshots = setupMocks({ wrapperFilename: 'gradlew.bat' });
       getPkgReleases.mockResolvedValueOnce(javaReleases);
diff --git a/lib/manager/gradle/deep/index.ts b/lib/manager/gradle/deep/index.ts
index 85dec5179d..103c893549 100644
--- a/lib/manager/gradle/deep/index.ts
+++ b/lib/manager/gradle/deep/index.ts
@@ -1,6 +1,6 @@
 import type { Stats } from 'fs';
 import upath from 'upath';
-import { getGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import { TEMPORARY_ERROR } from '../../../constants/error-messages';
 import * as datasourceMaven from '../../../datasource/maven';
 import { logger } from '../../../logger';
@@ -101,7 +101,7 @@ export async function extractAllPackageFiles(
 ): Promise<PackageFile[] | null> {
   let rootBuildGradle: string | undefined;
   let gradlew: Stats | null;
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   for (const packageFile of packageFiles) {
     const dirname = upath.dirname(packageFile);
     const gradlewPath = upath.join(dirname, gradleWrapperFileName());
diff --git a/lib/manager/gradle/deep/utils.ts b/lib/manager/gradle/deep/utils.ts
index b984d6c055..8f5d886a18 100644
--- a/lib/manager/gradle/deep/utils.ts
+++ b/lib/manager/gradle/deep/utils.ts
@@ -1,5 +1,5 @@
 import { join } from 'upath';
-import { getGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import { localPathExists, readLocalFile } from '../../../util/fs';
 import {
   extractGradleVersion,
@@ -11,7 +11,7 @@ const GradleWrapperProperties = 'gradle/wrapper/gradle-wrapper.properties';
 export async function getDockerConstraint(
   gradleRoot: string
 ): Promise<string | null> {
-  if (getGlobalConfig()?.binarySource !== 'docker') {
+  if (GlobalConfig.get('binarySource') !== 'docker') {
     // ignore
     return null;
   }
@@ -29,7 +29,7 @@ export async function getDockerConstraint(
 export async function getDockerPreCommands(
   gradleRoot: string
 ): Promise<string[]> {
-  if (getGlobalConfig()?.binarySource !== 'docker') {
+  if (GlobalConfig.get('binarySource') !== 'docker') {
     // ignore
     return null;
   }
diff --git a/lib/manager/helmv3/artifacts.spec.ts b/lib/manager/helmv3/artifacts.spec.ts
index 8b753abade..54238b5cfa 100644
--- a/lib/manager/helmv3/artifacts.spec.ts
+++ b/lib/manager/helmv3/artifacts.spec.ts
@@ -3,7 +3,7 @@ import _fs from 'fs-extra';
 import { join } from 'upath';
 import { envMock, mockExecAll } from '../../../test/exec-util';
 import { mocked } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import * as docker from '../../util/exec/docker';
 import * as _env from '../../util/exec/env';
@@ -31,11 +31,11 @@ describe('manager/helmv3/artifacts', () => {
     jest.resetModules();
 
     env.getChildProcessEnv.mockReturnValue(envMock.basic);
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
     docker.resetPrefetchedImages();
   });
   afterEach(() => {
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
   it('returns null if no Chart.lock found', async () => {
     const updatedDeps = [{ depName: 'dep1' }];
@@ -109,7 +109,7 @@ describe('manager/helmv3/artifacts', () => {
   });
 
   it('returns updated Chart.lock with docker', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     fs.readFile.mockResolvedValueOnce('Old Chart.lock' as never);
     const execSnapshots = mockExecAll(exec);
     fs.readFile.mockResolvedValueOnce('New Chart.lock' as never);
@@ -171,7 +171,7 @@ describe('manager/helmv3/artifacts', () => {
   });
 
   it('sets repositories from aliases with docker', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     fs.readFile.mockResolvedValueOnce('Old Chart.lock' as never);
     const execSnapshots = mockExecAll(exec);
     fs.readFile.mockResolvedValueOnce('New Chart.lock' as never);
diff --git a/lib/manager/jsonnet-bundler/artifacts.spec.ts b/lib/manager/jsonnet-bundler/artifacts.spec.ts
index f8f339fa0d..9b358b6fbb 100644
--- a/lib/manager/jsonnet-bundler/artifacts.spec.ts
+++ b/lib/manager/jsonnet-bundler/artifacts.spec.ts
@@ -1,7 +1,7 @@
 import { join } from 'upath';
 import { envMock, exec, mockExecAll } from '../../../test/exec-util';
 import { env, fs, git } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import type { StatusResult } from '../../util/git';
 import type { UpdateArtifactsConfig } from '../types';
@@ -23,7 +23,7 @@ describe('manager/jsonnet-bundler/artifacts', () => {
   beforeEach(() => {
     env.getChildProcessEnv.mockReturnValue(envMock.basic);
 
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
   });
 
   it('returns null if jsonnetfile.lock does not exist', async () => {
diff --git a/lib/manager/mix/artifacts.spec.ts b/lib/manager/mix/artifacts.spec.ts
index 8a43a5e7de..fb51593334 100644
--- a/lib/manager/mix/artifacts.spec.ts
+++ b/lib/manager/mix/artifacts.spec.ts
@@ -1,7 +1,7 @@
 import { join } from 'upath';
 import { envMock, exec, mockExecAll } from '../../../test/exec-util';
 import { env, fs, hostRules } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import * as docker from '../../util/exec/docker';
 import type { UpdateArtifactsConfig } from '../types';
@@ -25,11 +25,11 @@ describe('manager/mix/artifacts', () => {
     jest.resetModules();
 
     env.getChildProcessEnv.mockReturnValue(envMock.basic);
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
   });
 
   afterEach(() => {
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
 
   it('returns null if no mix.lock found', async () => {
@@ -82,7 +82,7 @@ describe('manager/mix/artifacts', () => {
 
   it('returns updated mix.lock', async () => {
     jest.spyOn(docker, 'removeDanglingContainers').mockResolvedValueOnce();
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     fs.readLocalFile.mockResolvedValueOnce('Old mix.lock');
     fs.findLocalSiblingOrParent.mockResolvedValueOnce('mix.lock');
     const execSnapshots = mockExecAll(exec);
@@ -100,7 +100,7 @@ describe('manager/mix/artifacts', () => {
 
   it('authenticates to private repositories', async () => {
     jest.spyOn(docker, 'removeDanglingContainers').mockResolvedValueOnce();
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     fs.readLocalFile.mockResolvedValueOnce('Old mix.lock');
     fs.findLocalSiblingOrParent.mockResolvedValueOnce('mix.lock');
     const execSnapshots = mockExecAll(exec);
@@ -140,7 +140,7 @@ describe('manager/mix/artifacts', () => {
   });
 
   it('returns updated mix.lock in subdir', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     fs.findLocalSiblingOrParent.mockResolvedValueOnce('subdir/mix.lock');
     mockExecAll(exec);
     expect(
diff --git a/lib/manager/mix/extract.spec.ts b/lib/manager/mix/extract.spec.ts
index 009fd1a560..107ca67b62 100644
--- a/lib/manager/mix/extract.spec.ts
+++ b/lib/manager/mix/extract.spec.ts
@@ -1,12 +1,12 @@
 import { loadFixture } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { extractPackageFile } from '.';
 
 const sample = loadFixture('mix.exs');
 
 describe('manager/mix/extract', () => {
   beforeEach(() => {
-    setGlobalConfig({ localDir: '' });
+    GlobalConfig.set({ localDir: '' });
   });
 
   describe('extractPackageFile()', () => {
diff --git a/lib/manager/npm/extract/index.ts b/lib/manager/npm/extract/index.ts
index a1589c6bf2..143c8de05b 100644
--- a/lib/manager/npm/extract/index.ts
+++ b/lib/manager/npm/extract/index.ts
@@ -1,6 +1,6 @@
 import is from '@sindresorhus/is';
 import validateNpmPackageName from 'validate-npm-package-name';
-import { getGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import { CONFIG_VALIDATION } from '../../../constants/error-messages';
 import * as datasourceGithubTags from '../../../datasource/github-tags';
 import { id as npmId } from '../../../datasource/npm';
@@ -113,7 +113,7 @@ export async function extractPackageFile(
         logger.debug('Stripping package-lock setting from .npmrc');
         repoNpmrc = repoNpmrc.replace(/(^|\n)package-lock.*?(\n|$)/g, '\n'); // TODO #12070
       }
-      if (repoNpmrc.includes('=${') && !getGlobalConfig().exposeAllEnv) {
+      if (repoNpmrc.includes('=${') && !GlobalConfig.get('exposeAllEnv')) {
         logger.debug(
           { npmrcFileName },
           'Stripping .npmrc file of lines with variables'
diff --git a/lib/manager/npm/extract/pnpm.spec.ts b/lib/manager/npm/extract/pnpm.spec.ts
index 99d9918198..650ef27401 100644
--- a/lib/manager/npm/extract/pnpm.spec.ts
+++ b/lib/manager/npm/extract/pnpm.spec.ts
@@ -1,6 +1,6 @@
 import yaml from 'js-yaml';
 import { getFixturePath, logger } from '../../../../test/util';
-import { setGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import * as fs from '../../../util/fs';
 import {
   detectPnpmWorkspaces,
@@ -10,7 +10,7 @@ import {
 
 describe('manager/npm/extract/pnpm', () => {
   beforeAll(() => {
-    setGlobalConfig({ localDir: getFixturePath('pnpm-monorepo/', '..') });
+    GlobalConfig.set({ localDir: getFixturePath('pnpm-monorepo/', '..') });
   });
 
   describe('.extractPnpmFilters()', () => {
diff --git a/lib/manager/npm/post-update/index.ts b/lib/manager/npm/post-update/index.ts
index b957e78e03..03ec150b52 100644
--- a/lib/manager/npm/post-update/index.ts
+++ b/lib/manager/npm/post-update/index.ts
@@ -3,7 +3,7 @@ import deepmerge from 'deepmerge';
 import detectIndent from 'detect-indent';
 import { dump, load } from 'js-yaml';
 import upath from 'upath';
-import { getGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import { SYSTEM_INSUFFICIENT_DISK_SPACE } from '../../../constants/error-messages';
 import { id as npmId } from '../../../datasource/npm';
 import { logger } from '../../../logger';
@@ -141,7 +141,7 @@ export async function writeExistingFiles(
     { packageFiles: npmFiles.map((n) => n.packageFile) },
     'Writing package.json files'
   );
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   for (const packageFile of npmFiles) {
     const basedir = upath.join(
       localDir,
@@ -251,7 +251,7 @@ export async function writeUpdatedPackageFiles(
     logger.debug('No files found');
     return;
   }
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   for (const packageFile of config.updatedPackageFiles) {
     if (packageFile.name.endsWith('package-lock.json')) {
       logger.debug(`Writing package-lock file: ${packageFile.name}`);
@@ -510,7 +510,7 @@ export async function getAdditionalFiles(
   } catch (err) {
     logger.warn({ err }, 'Error getting token for packageFile');
   }
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   for (const npmLock of dirs.npmLockDirs) {
     const lockFileDir = upath.dirname(npmLock);
     const fullLockFileDir = upath.join(localDir, lockFileDir);
diff --git a/lib/manager/npm/post-update/lerna.spec.ts b/lib/manager/npm/post-update/lerna.spec.ts
index 511153de43..d48fa30fd5 100644
--- a/lib/manager/npm/post-update/lerna.spec.ts
+++ b/lib/manager/npm/post-update/lerna.spec.ts
@@ -1,7 +1,7 @@
 import { exec as _exec } from 'child_process';
 import { envMock, mockExecAll } from '../../../../test/exec-util';
 import { mocked } from '../../../../test/util';
-import { setGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import * as _env from '../../../util/exec/env';
 import * as _lernaHelper from './lerna';
 
@@ -95,7 +95,7 @@ describe('manager/npm/post-update/lerna', () => {
     });
     it('allows scripts for trust level high', async () => {
       const execSnapshots = mockExecAll(exec);
-      setGlobalConfig({ allowScripts: true });
+      GlobalConfig.set({ allowScripts: true });
       const res = await lernaHelper.generateLockFiles(
         lernaPkgFile('npm'),
         'some-dir',
diff --git a/lib/manager/npm/post-update/lerna.ts b/lib/manager/npm/post-update/lerna.ts
index a7b3329cce..d696d03a8c 100644
--- a/lib/manager/npm/post-update/lerna.ts
+++ b/lib/manager/npm/post-update/lerna.ts
@@ -1,6 +1,6 @@
 import semver, { validRange } from 'semver';
 import { quote } from 'shlex';
-import { getGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import { TEMPORARY_ERROR } from '../../../constants/error-messages';
 import { logger } from '../../../logger';
 import { ExecOptions, exec } from '../../../util/exec';
@@ -67,7 +67,7 @@ export async function generateLockFiles(
       return { error: false };
     }
     let lernaCommand = `lerna bootstrap --no-ci --ignore-scripts -- `;
-    if (getGlobalConfig().allowScripts && config.ignoreScripts !== false) {
+    if (GlobalConfig.get('allowScripts') && config.ignoreScripts !== false) {
       cmdOptions = cmdOptions.replace('--ignore-scripts ', '');
       lernaCommand = lernaCommand.replace('--ignore-scripts ', '');
     }
@@ -87,7 +87,7 @@ export async function generateLockFiles(
       },
     };
     // istanbul ignore if
-    if (getGlobalConfig().exposeAllEnv) {
+    if (GlobalConfig.get('exposeAllEnv')) {
       execOptions.extraEnv.NPM_AUTH = env.NPM_AUTH;
       execOptions.extraEnv.NPM_EMAIL = env.NPM_EMAIL;
     }
diff --git a/lib/manager/npm/post-update/npm.ts b/lib/manager/npm/post-update/npm.ts
index f84023593c..8adfe5698a 100644
--- a/lib/manager/npm/post-update/npm.ts
+++ b/lib/manager/npm/post-update/npm.ts
@@ -1,7 +1,7 @@
 import { validRange } from 'semver';
 import { quote } from 'shlex';
 import { join } from 'upath';
-import { getGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import {
   SYSTEM_INSUFFICIENT_DISK_SPACE,
   TEMPORARY_ERROR,
@@ -52,7 +52,7 @@ export async function generateLockFile(
       cmdOptions += '--package-lock-only --no-audit';
     }
 
-    if (!getGlobalConfig().allowScripts || config.ignoreScripts) {
+    if (!GlobalConfig.get('allowScripts') || config.ignoreScripts) {
       cmdOptions += ' --ignore-scripts';
     }
 
@@ -71,7 +71,7 @@ export async function generateLockFile(
       },
     };
     // istanbul ignore if
-    if (getGlobalConfig().exposeAllEnv) {
+    if (GlobalConfig.get('exposeAllEnv')) {
       execOptions.extraEnv.NPM_AUTH = env.NPM_AUTH;
       execOptions.extraEnv.NPM_EMAIL = env.NPM_EMAIL;
     }
diff --git a/lib/manager/npm/post-update/pnpm.ts b/lib/manager/npm/post-update/pnpm.ts
index 7d0f62ba40..1e9c030bc3 100644
--- a/lib/manager/npm/post-update/pnpm.ts
+++ b/lib/manager/npm/post-update/pnpm.ts
@@ -1,7 +1,7 @@
 import { validRange } from 'semver';
 import { quote } from 'shlex';
 import { join } from 'upath';
-import { getGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import { TEMPORARY_ERROR } from '../../../constants/error-messages';
 import { logger } from '../../../logger';
 import { ExecOptions, exec } from '../../../util/exec';
@@ -50,13 +50,13 @@ export async function generateLockFile(
       },
     };
     // istanbul ignore if
-    if (getGlobalConfig().exposeAllEnv) {
+    if (GlobalConfig.get('exposeAllEnv')) {
       execOptions.extraEnv.NPM_AUTH = env.NPM_AUTH;
       execOptions.extraEnv.NPM_EMAIL = env.NPM_EMAIL;
     }
     cmd = 'pnpm';
     let args = 'install --recursive --lockfile-only';
-    if (!getGlobalConfig().allowScripts || config.ignoreScripts) {
+    if (!GlobalConfig.get('allowScripts') || config.ignoreScripts) {
       args += ' --ignore-scripts';
       args += ' --ignore-pnpmfile';
     }
diff --git a/lib/manager/npm/post-update/yarn.ts b/lib/manager/npm/post-update/yarn.ts
index 60d4fd550f..e98cc18911 100644
--- a/lib/manager/npm/post-update/yarn.ts
+++ b/lib/manager/npm/post-update/yarn.ts
@@ -2,7 +2,7 @@ import is from '@sindresorhus/is';
 import { gte, minVersion, validRange } from 'semver';
 import { quote } from 'shlex';
 import { join } from 'upath';
-import { getGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import {
   SYSTEM_INSUFFICIENT_DISK_SPACE,
   TEMPORARY_ERROR,
@@ -128,7 +128,7 @@ export async function generateLockFile(
         extraEnv.YARN_ENABLE_GLOBAL_CACHE = '1';
       }
     }
-    if (!getGlobalConfig().allowScripts || config.ignoreScripts) {
+    if (!GlobalConfig.get('allowScripts') || config.ignoreScripts) {
       if (isYarn1) {
         cmdOptions += ' --ignore-scripts';
       } else if (isYarnModeAvailable) {
@@ -152,7 +152,7 @@ export async function generateLockFile(
       },
     };
     // istanbul ignore if
-    if (getGlobalConfig().exposeAllEnv) {
+    if (GlobalConfig.get('exposeAllEnv')) {
       execOptions.extraEnv.NPM_AUTH = env.NPM_AUTH;
       execOptions.extraEnv.NPM_EMAIL = env.NPM_EMAIL;
     }
diff --git a/lib/manager/nuget/artifacts.spec.ts b/lib/manager/nuget/artifacts.spec.ts
index 64f0890d16..71824a6dc7 100644
--- a/lib/manager/nuget/artifacts.spec.ts
+++ b/lib/manager/nuget/artifacts.spec.ts
@@ -2,7 +2,7 @@ import { exec as _exec } from 'child_process';
 import { join } from 'upath';
 import { envMock, mockExecAll } from '../../../test/exec-util';
 import { fs, mocked } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import * as docker from '../../util/exec/docker';
 import * as _env from '../../util/exec/env';
@@ -49,12 +49,12 @@ describe('manager/nuget/artifacts', () => {
       Promise.resolve(`others/${dirName}`)
     );
     getRandomString.mockReturnValue('not-so-random' as any);
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
     docker.resetPrefetchedImages();
   });
 
   afterEach(() => {
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
 
   it('aborts if no lock file found', async () => {
@@ -150,7 +150,7 @@ describe('manager/nuget/artifacts', () => {
   });
 
   it('supports docker mode', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     const execSnapshots = mockExecAll(exec);
     fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json');
     fs.readLocalFile.mockResolvedValueOnce('Current packages.lock.json' as any);
@@ -166,7 +166,7 @@ describe('manager/nuget/artifacts', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('supports global mode', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'global' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'global' });
     const execSnapshots = mockExecAll(exec);
     fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json');
     fs.readLocalFile.mockResolvedValueOnce('Current packages.lock.json' as any);
diff --git a/lib/manager/nuget/artifacts.ts b/lib/manager/nuget/artifacts.ts
index 34a910aad3..acd693db32 100644
--- a/lib/manager/nuget/artifacts.ts
+++ b/lib/manager/nuget/artifacts.ts
@@ -1,5 +1,5 @@
 import { join } from 'path';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { TEMPORARY_ERROR } from '../../constants/error-messages';
 import { id, parseRegistryUrl } from '../../datasource/nuget';
 import { logger } from '../../logger';
@@ -30,7 +30,7 @@ async function addSourceCmds(
   config: UpdateArtifactsConfig,
   nugetConfigFile: string
 ): Promise<string[]> {
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   const registries =
     (await getConfiguredRegistries(packageFileName, localDir)) ||
     getDefaultRegistries();
diff --git a/lib/manager/nuget/extract.spec.ts b/lib/manager/nuget/extract.spec.ts
index 587d4c61f4..e12e01825c 100644
--- a/lib/manager/nuget/extract.spec.ts
+++ b/lib/manager/nuget/extract.spec.ts
@@ -1,6 +1,6 @@
 import * as upath from 'upath';
 import { loadFixture } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import type { ExtractConfig } from '../types';
 import { extractPackageFile } from './extract';
@@ -14,10 +14,10 @@ const adminConfig: RepoGlobalConfig = {
 describe('manager/nuget/extract', () => {
   describe('extractPackageFile()', () => {
     beforeEach(() => {
-      setGlobalConfig(adminConfig);
+      GlobalConfig.set(adminConfig);
     });
     afterEach(() => {
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
     it('returns empty for invalid csproj', async () => {
       expect(await extractPackageFile('nothing here', 'bogus', config)).toEqual(
diff --git a/lib/manager/nuget/extract.ts b/lib/manager/nuget/extract.ts
index b110aa8346..fb4f4fc130 100644
--- a/lib/manager/nuget/extract.ts
+++ b/lib/manager/nuget/extract.ts
@@ -1,5 +1,5 @@
 import { XmlDocument, XmlElement, XmlNode } from 'xmldoc';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import * as datasourceNuget from '../../datasource/nuget';
 import { logger } from '../../logger';
 import { getSiblingFileName, localPathExists } from '../../util/fs';
@@ -72,7 +72,7 @@ export async function extractPackageFile(
 ): Promise<PackageFile | null> {
   logger.trace({ packageFile }, 'nuget.extractPackageFile()');
 
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   const registries = await getConfiguredRegistries(packageFile, localDir);
   const registryUrls = registries
     ? registries.map((registry) => registry.url)
diff --git a/lib/manager/pip-compile/artifacts.spec.ts b/lib/manager/pip-compile/artifacts.spec.ts
index 4f62a4899d..fff86aa9d0 100644
--- a/lib/manager/pip-compile/artifacts.spec.ts
+++ b/lib/manager/pip-compile/artifacts.spec.ts
@@ -3,7 +3,7 @@ import _fs from 'fs-extra';
 import { join } from 'upath';
 import { envMock, mockExecAll } from '../../../test/exec-util';
 import { git, mocked } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import * as docker from '../../util/exec/docker';
 import * as _env from '../../util/exec/env';
@@ -40,7 +40,7 @@ describe('manager/pip-compile/artifacts', () => {
       LANG: 'en_US.UTF-8',
       LC_ALL: 'en_US',
     });
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
     docker.resetPrefetchedImages();
   });
 
@@ -89,7 +89,7 @@ describe('manager/pip-compile/artifacts', () => {
   });
 
   it('supports docker mode', async () => {
-    setGlobalConfig(dockerAdminConfig);
+    GlobalConfig.set(dockerAdminConfig);
     const execSnapshots = mockExecAll(exec);
     git.getRepoStatus.mockResolvedValue({
       modified: ['requirements.txt'],
@@ -144,7 +144,7 @@ describe('manager/pip-compile/artifacts', () => {
   });
 
   it('uses pipenv version from config', async () => {
-    setGlobalConfig(dockerAdminConfig);
+    GlobalConfig.set(dockerAdminConfig);
     const execSnapshots = mockExecAll(exec);
     git.getRepoStatus.mockResolvedValue({
       modified: ['requirements.txt'],
diff --git a/lib/manager/pip_requirements/artifacts.spec.ts b/lib/manager/pip_requirements/artifacts.spec.ts
index 798025ef7a..61af191fa3 100644
--- a/lib/manager/pip_requirements/artifacts.spec.ts
+++ b/lib/manager/pip_requirements/artifacts.spec.ts
@@ -1,5 +1,5 @@
 import _fs from 'fs-extra';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { UpdateArtifactsConfig } from '../types';
 import { updateArtifacts } from './artifacts';
 
@@ -19,7 +19,7 @@ describe('manager/pip_requirements/artifacts', () => {
   beforeEach(() => {
     jest.resetAllMocks();
     jest.resetModules();
-    setGlobalConfig({ localDir: '' });
+    GlobalConfig.set({ localDir: '' });
   });
   it('returns null if no updatedDeps were provided', async () => {
     expect(
diff --git a/lib/manager/pip_requirements/extract.spec.ts b/lib/manager/pip_requirements/extract.spec.ts
index 551aec84e3..94f9b02783 100644
--- a/lib/manager/pip_requirements/extract.spec.ts
+++ b/lib/manager/pip_requirements/extract.spec.ts
@@ -1,5 +1,5 @@
 import { loadFixture } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { extractPackageFile } from './extract';
 
 const requirements1 = loadFixture('requirements1.txt');
@@ -14,11 +14,11 @@ const requirements8 = loadFixture('requirements8.txt');
 describe('manager/pip_requirements/extract', () => {
   beforeEach(() => {
     delete process.env.PIP_TEST_TOKEN;
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
   afterEach(() => {
     delete process.env.PIP_TEST_TOKEN;
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
   describe('extractPackageFile()', () => {
     let config;
@@ -124,7 +124,7 @@ describe('manager/pip_requirements/extract', () => {
     });
     it('should replace env vars in high trust mode', () => {
       process.env.PIP_TEST_TOKEN = 'its-a-secret';
-      setGlobalConfig({ exposeAllEnv: true });
+      GlobalConfig.set({ exposeAllEnv: true });
       const res = extractPackageFile(requirements7, 'unused_file_name', {});
       expect(res.registryUrls).toEqual([
         'https://pypi.org/pypi/',
diff --git a/lib/manager/pip_requirements/extract.ts b/lib/manager/pip_requirements/extract.ts
index 047fbcfe9d..c10ab1966e 100644
--- a/lib/manager/pip_requirements/extract.ts
+++ b/lib/manager/pip_requirements/extract.ts
@@ -1,6 +1,6 @@
 // based on https://www.python.org/dev/peps/pep-0508/#names
 import { RANGE_PATTERN } from '@renovate/pep440/lib/specifier';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { PypiDatasource } from '../../datasource/pypi';
 import { logger } from '../../logger';
 import { SkipReason } from '../../types';
@@ -91,7 +91,7 @@ export function extractPackageFile(
     res.registryUrls = registryUrls.map((url) => {
       // handle the optional quotes in eg. `--extra-index-url "https://foo.bar"`
       const cleaned = url.replace(regEx(/^"/), '').replace(regEx(/"$/), ''); // TODO #12071
-      if (!getGlobalConfig().exposeAllEnv) {
+      if (!GlobalConfig.get('exposeAllEnv')) {
         return cleaned;
       }
       // interpolate any environment variables
diff --git a/lib/manager/pipenv/artifacts.spec.ts b/lib/manager/pipenv/artifacts.spec.ts
index fbacc6945a..c8351a8759 100644
--- a/lib/manager/pipenv/artifacts.spec.ts
+++ b/lib/manager/pipenv/artifacts.spec.ts
@@ -3,7 +3,7 @@ import _fs from 'fs-extra';
 import { join } from 'upath';
 import { envMock, mockExecAll } from '../../../test/exec-util';
 import { git, mocked } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import * as docker from '../../util/exec/docker';
 import * as _env from '../../util/exec/env';
@@ -42,7 +42,7 @@ describe('manager/pipenv/artifacts', () => {
       LC_ALL: 'en_US',
     });
 
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
     docker.resetPrefetchedImages();
     pipFileLock = {
       _meta: { requires: {} },
@@ -108,7 +108,7 @@ describe('manager/pipenv/artifacts', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('supports docker mode', async () => {
-    setGlobalConfig(dockerAdminConfig);
+    GlobalConfig.set(dockerAdminConfig);
     pipFileLock._meta.requires.python_version = '3.7';
     fs.readFile.mockResolvedValueOnce(JSON.stringify(pipFileLock) as any);
     const execSnapshots = mockExecAll(exec);
@@ -160,7 +160,7 @@ describe('manager/pipenv/artifacts', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('uses pipenv version from Pipfile', async () => {
-    setGlobalConfig(dockerAdminConfig);
+    GlobalConfig.set(dockerAdminConfig);
     pipFileLock.default.pipenv.version = '==2020.8.13';
     fs.readFile.mockResolvedValueOnce(JSON.stringify(pipFileLock) as any);
     const execSnapshots = mockExecAll(exec);
@@ -179,7 +179,7 @@ describe('manager/pipenv/artifacts', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('uses pipenv version from Pipfile dev packages', async () => {
-    setGlobalConfig(dockerAdminConfig);
+    GlobalConfig.set(dockerAdminConfig);
     pipFileLock.develop.pipenv.version = '==2020.8.13';
     fs.readFile.mockResolvedValueOnce(JSON.stringify(pipFileLock) as any);
     const execSnapshots = mockExecAll(exec);
@@ -198,7 +198,7 @@ describe('manager/pipenv/artifacts', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('uses pipenv version from config', async () => {
-    setGlobalConfig(dockerAdminConfig);
+    GlobalConfig.set(dockerAdminConfig);
     pipFileLock.default.pipenv.version = '==2020.8.13';
     fs.readFile.mockResolvedValueOnce(JSON.stringify(pipFileLock) as any);
     const execSnapshots = mockExecAll(exec);
diff --git a/lib/manager/poetry/artifacts.spec.ts b/lib/manager/poetry/artifacts.spec.ts
index 69f3922faf..02440b20e9 100644
--- a/lib/manager/poetry/artifacts.spec.ts
+++ b/lib/manager/poetry/artifacts.spec.ts
@@ -3,7 +3,7 @@ import _fs from 'fs-extra';
 import { join } from 'upath';
 import { envMock, mockExecAll } from '../../../test/exec-util';
 import { loadFixture, mocked } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import * as _datasource from '../../datasource';
 import * as docker from '../../util/exec/docker';
@@ -36,7 +36,7 @@ describe('manager/poetry/artifacts', () => {
   beforeEach(() => {
     jest.resetAllMocks();
     env.getChildProcessEnv.mockReturnValue(envMock.basic);
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
     docker.resetPrefetchedImages();
   });
   it('returns null if no poetry.lock found', async () => {
@@ -130,7 +130,7 @@ describe('manager/poetry/artifacts', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('returns updated poetry.lock using docker', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     fs.readFile.mockResolvedValueOnce('[metadata]\n' as any);
     const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('New poetry.lock' as any);
@@ -155,7 +155,7 @@ describe('manager/poetry/artifacts', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('returns updated poetry.lock using docker (constraints)', async () => {
-    setGlobalConfig({ ...adminConfig, binarySource: 'docker' });
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
     fs.readFile.mockResolvedValueOnce(
       '[metadata]\npython-versions = "~2.7 || ^3.4"' as any
     );
diff --git a/lib/manager/terraform/extract.spec.ts b/lib/manager/terraform/extract.spec.ts
index 109092ffc3..fe6f6f4b1f 100644
--- a/lib/manager/terraform/extract.spec.ts
+++ b/lib/manager/terraform/extract.spec.ts
@@ -1,6 +1,6 @@
 import { join } from 'upath';
 import { fs, loadFixture } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import { extractPackageFile } from '.';
 
@@ -25,7 +25,7 @@ jest.mock('../../util/fs');
 
 describe('manager/terraform/extract', () => {
   beforeEach(() => {
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
   });
   describe('extractPackageFile()', () => {
     it('returns null for empty', async () => {
diff --git a/lib/manager/terraform/lockfile/hash.spec.ts b/lib/manager/terraform/lockfile/hash.spec.ts
index d14da9c54c..b2b0675752 100644
--- a/lib/manager/terraform/lockfile/hash.spec.ts
+++ b/lib/manager/terraform/lockfile/hash.spec.ts
@@ -2,7 +2,7 @@ import { createReadStream } from 'fs';
 import { DirectoryResult, dir } from 'tmp-promise';
 import * as httpMock from '../../../../test/http-mock';
 import { getFixturePath, loadFixture, logger } from '../../../../test/util';
-import { setGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import { TerraformProviderDatasource } from '../../../datasource/terraform-provider';
 import { Logger } from '../../../logger/types';
 import { TerraformProviderHash } from './hash';
@@ -17,7 +17,7 @@ describe('manager/terraform/lockfile/hash', () => {
 
   beforeEach(async () => {
     cacheDir = await dir({ unsafeCleanup: true });
-    setGlobalConfig({ cacheDir: cacheDir.path });
+    GlobalConfig.set({ cacheDir: cacheDir.path });
   });
 
   afterEach(() => cacheDir.cleanup());
diff --git a/lib/manager/terraform/lockfile/index.spec.ts b/lib/manager/terraform/lockfile/index.spec.ts
index 2964b081bd..0c2a4d52c1 100644
--- a/lib/manager/terraform/lockfile/index.spec.ts
+++ b/lib/manager/terraform/lockfile/index.spec.ts
@@ -1,6 +1,6 @@
 import { join } from 'upath';
 import { fs, loadFixture, mocked } from '../../../../test/util';
-import { setGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import { getPkgReleases } from '../../../datasource';
 import type { UpdateArtifactsConfig } from '../../types';
 import { TerraformProviderHash } from './hash';
@@ -33,7 +33,7 @@ describe('manager/terraform/lockfile/index', () => {
   beforeEach(() => {
     jest.resetAllMocks();
     jest.resetModules();
-    setGlobalConfig(adminConfig);
+    GlobalConfig.set(adminConfig);
   });
 
   it('returns null if no .terraform.lock.hcl found', async () => {
diff --git a/lib/util/cache/repository/index.spec.ts b/lib/util/cache/repository/index.spec.ts
index 2fe02ba3cd..af06921428 100644
--- a/lib/util/cache/repository/index.spec.ts
+++ b/lib/util/cache/repository/index.spec.ts
@@ -1,6 +1,6 @@
 import * as _fs from 'fs-extra';
 import { mocked } from '../../../../test/util';
-import { setGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import * as repositoryCache from '.';
 
 jest.mock('fs-extra');
@@ -10,7 +10,7 @@ const fs = mocked(_fs);
 describe('util/cache/repository/index', () => {
   beforeEach(() => {
     jest.resetAllMocks();
-    setGlobalConfig({ cacheDir: '/tmp/renovate/cache/' });
+    GlobalConfig.set({ cacheDir: '/tmp/renovate/cache/' });
   });
   const config = {
     platform: 'github',
diff --git a/lib/util/cache/repository/index.ts b/lib/util/cache/repository/index.ts
index cd726ab7e0..415b629e4b 100644
--- a/lib/util/cache/repository/index.ts
+++ b/lib/util/cache/repository/index.ts
@@ -1,6 +1,6 @@
 import * as fs from 'fs-extra';
 import { join } from 'upath';
-import { getGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import type {
   RenovateConfig,
   RepositoryCacheConfig,
@@ -17,7 +17,7 @@ let cache: Cache = Object.create({});
 
 export function getCacheFileName(config: RenovateConfig): string {
   return join(
-    getGlobalConfig().cacheDir,
+    GlobalConfig.get('cacheDir'),
     '/renovate/repository/',
     config.platform,
     config.repository + '.json'
diff --git a/lib/util/exec/docker/index.spec.ts b/lib/util/exec/docker/index.spec.ts
index 4c8d4dea7d..cd66624881 100644
--- a/lib/util/exec/docker/index.spec.ts
+++ b/lib/util/exec/docker/index.spec.ts
@@ -3,7 +3,7 @@ import {
   mockExecAll,
   mockExecSequence,
 } from '../../../../test/exec-util';
-import { setGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import { SYSTEM_INSUFFICIENT_MEMORY } from '../../../constants/error-messages';
 import { getPkgReleases as _getPkgReleases } from '../../../datasource';
 import { logger } from '../../../logger';
@@ -141,12 +141,12 @@ describe('util/exec/docker/index', () => {
 
   describe('removeDanglingContainers', () => {
     beforeEach(() => {
-      setGlobalConfig({ binarySource: 'docker' });
+      GlobalConfig.set({ binarySource: 'docker' });
     });
 
     it('short-circuits in non-Docker environment', async () => {
       const execSnapshots = mockExecAll(exec);
-      setGlobalConfig({ binarySource: 'global' });
+      GlobalConfig.set({ binarySource: 'global' });
       await removeDanglingContainers();
       expect(execSnapshots).toBeEmpty();
     });
@@ -231,7 +231,7 @@ describe('util/exec/docker/index', () => {
       `bash -l -c "foo && bar && baz"`;
 
     beforeEach(() => {
-      setGlobalConfig({ dockerUser: 'some-user' });
+      GlobalConfig.set({ dockerUser: 'some-user' });
     });
 
     it('returns executable command', async () => {
diff --git a/lib/util/exec/docker/index.ts b/lib/util/exec/docker/index.ts
index 25646de239..dc921146ac 100644
--- a/lib/util/exec/docker/index.ts
+++ b/lib/util/exec/docker/index.ts
@@ -1,5 +1,5 @@
 import is from '@sindresorhus/is';
-import { getGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import { SYSTEM_INSUFFICIENT_MEMORY } from '../../../constants/error-messages';
 import { getPkgReleases } from '../../../datasource';
 import { logger } from '../../../logger';
@@ -159,7 +159,7 @@ export async function removeDockerContainer(
 }
 
 export async function removeDanglingContainers(): Promise<void> {
-  const { binarySource, dockerChildPrefix } = getGlobalConfig();
+  const { binarySource, dockerChildPrefix } = GlobalConfig.get();
   if (binarySource !== 'docker') {
     return;
   }
@@ -212,7 +212,7 @@ export async function generateDockerCommand(
     dockerUser,
     dockerChildPrefix,
     dockerImagePrefix,
-  } = getGlobalConfig();
+  } = GlobalConfig.get();
   const result = ['docker run --rm'];
   const containerName = getContainerName(image, dockerChildPrefix);
   const containerLabel = getContainerLabel(dockerChildPrefix);
diff --git a/lib/util/exec/env.spec.ts b/lib/util/exec/env.spec.ts
index d6a4d09bf5..ac221486e9 100644
--- a/lib/util/exec/env.spec.ts
+++ b/lib/util/exec/env.spec.ts
@@ -1,4 +1,4 @@
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { getChildProcessEnv } from './env';
 
 describe('util/exec/env', () => {
@@ -54,7 +54,7 @@ describe('util/exec/env', () => {
 
   describe('getChildProcessEnv when trustlevel set to high', () => {
     it('returns process.env if trustlevel set to high', () => {
-      setGlobalConfig({ exposeAllEnv: true });
+      GlobalConfig.set({ exposeAllEnv: true });
       expect(getChildProcessEnv()).toMatchObject(process.env);
     });
   });
diff --git a/lib/util/exec/env.ts b/lib/util/exec/env.ts
index f453bd34ab..7fef55acf6 100644
--- a/lib/util/exec/env.ts
+++ b/lib/util/exec/env.ts
@@ -1,4 +1,4 @@
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 
 const basicEnvVars = [
   'HTTP_PROXY',
@@ -20,7 +20,7 @@ export function getChildProcessEnv(
   customEnvVars: string[] = []
 ): NodeJS.ProcessEnv {
   const env: NodeJS.ProcessEnv = {};
-  if (getGlobalConfig().exposeAllEnv) {
+  if (GlobalConfig.get('exposeAllEnv')) {
     return { ...env, ...process.env };
   }
   const envVars = [...basicEnvVars, ...customEnvVars];
diff --git a/lib/util/exec/index.spec.ts b/lib/util/exec/index.spec.ts
index 86a61a213a..eca56e89d9 100644
--- a/lib/util/exec/index.spec.ts
+++ b/lib/util/exec/index.spec.ts
@@ -3,7 +3,7 @@ import {
   exec as _cpExec,
 } from 'child_process';
 import { envMock } from '../../../test/exec-util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import { TEMPORARY_ERROR } from '../../constants/error-messages';
 import { RawExecOptions, VolumeOption } from './common';
@@ -38,7 +38,7 @@ describe('util/exec/index', () => {
     jest.restoreAllMocks();
     jest.resetModules();
     processEnvOrig = process.env;
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
 
   afterEach(() => {
@@ -697,7 +697,7 @@ describe('util/exec/index', () => {
       callback(null, { stdout: '', stderr: '' });
       return undefined;
     });
-    setGlobalConfig({ cacheDir, localDir: cwd, ...adminConfig });
+    GlobalConfig.set({ cacheDir, localDir: cwd, ...adminConfig });
     await exec(cmd as string, inOpts);
 
     expect(actualCmd).toEqual(outCommand);
@@ -714,19 +714,19 @@ describe('util/exec/index', () => {
       return undefined;
     });
 
-    setGlobalConfig({ binarySource: 'global' });
+    GlobalConfig.set({ binarySource: 'global' });
     await exec(inCmd, { docker });
     await exec(inCmd, { docker });
 
-    setGlobalConfig({ binarySource: 'docker' });
+    GlobalConfig.set({ binarySource: 'docker' });
     await exec(inCmd, { docker });
     await exec(inCmd, { docker });
 
-    setGlobalConfig({ binarySource: 'global' });
+    GlobalConfig.set({ binarySource: 'global' });
     await exec(inCmd, { docker });
     await exec(inCmd, { docker });
 
-    setGlobalConfig({ binarySource: 'docker' });
+    GlobalConfig.set({ binarySource: 'docker' });
     await exec(inCmd, { docker });
     await exec(inCmd, { docker });
 
@@ -750,7 +750,7 @@ describe('util/exec/index', () => {
   });
 
   it('wraps error if removeDockerContainer throws an error', async () => {
-    setGlobalConfig({ binarySource: 'docker' });
+    GlobalConfig.set({ binarySource: 'docker' });
     cpExec.mockImplementation(() => {
       throw new Error('some error occurred');
     });
diff --git a/lib/util/exec/index.ts b/lib/util/exec/index.ts
index c093e0b059..221790b16a 100644
--- a/lib/util/exec/index.ts
+++ b/lib/util/exec/index.ts
@@ -1,6 +1,6 @@
 import type { ExecOptions as ChildProcessExecOptions } from 'child_process';
 import { dirname, join } from 'upath';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { TEMPORARY_ERROR } from '../../constants/error-messages';
 import { logger } from '../../logger';
 import { generateInstallCommands } from './buildpack';
@@ -28,7 +28,7 @@ function getChildEnv({
   extraEnv = {},
   env: forcedEnv = {},
 }: ExecOptions): ExtraEnv<string> {
-  const { customEnvVariables: globalConfigEnv } = getGlobalConfig();
+  const globalConfigEnv = GlobalConfig.get('customEnvVariables');
 
   const inheritedKeys = Object.entries(extraEnv).reduce(
     (acc, [key, val]) =>
@@ -62,7 +62,7 @@ function dockerEnvVars(
 }
 
 function getCwd({ cwd, cwdFile }: ExecOptions): string {
-  const { localDir: defaultCwd } = getGlobalConfig();
+  const defaultCwd = GlobalConfig.get('localDir');
   const paramCwd = cwdFile ? join(defaultCwd, dirname(cwdFile)) : cwd;
   return paramCwd || defaultCwd;
 }
@@ -90,7 +90,7 @@ function getRawExecOptions(opts: ExecOptions): RawExecOptions {
 }
 
 function isDocker({ docker }: ExecOptions): boolean {
-  const { binarySource } = getGlobalConfig();
+  const { binarySource } = GlobalConfig.get();
   return binarySource === 'docker' && !!docker;
 }
 
@@ -104,7 +104,7 @@ async function prepareRawExec(
   opts: ExecOptions = {}
 ): Promise<RawExecArguments> {
   const { docker } = opts;
-  const { customEnvVariables } = getGlobalConfig();
+  const { customEnvVariables } = GlobalConfig.get();
 
   const rawOptions = getRawExecOptions(opts);
 
@@ -136,7 +136,7 @@ export async function exec(
   opts: ExecOptions = {}
 ): Promise<ExecResult> {
   const { docker } = opts;
-  const { dockerChildPrefix } = getGlobalConfig();
+  const { dockerChildPrefix } = GlobalConfig.get();
 
   const { rawCommands, rawOptions } = await prepareRawExec(cmd, opts);
   const useDocker = isDocker(opts);
diff --git a/lib/util/fs/index.spec.ts b/lib/util/fs/index.spec.ts
index 1a7732410c..6275b0edb8 100644
--- a/lib/util/fs/index.spec.ts
+++ b/lib/util/fs/index.spec.ts
@@ -2,7 +2,7 @@ import { withDir } from 'tmp-promise';
 import { join } from 'upath';
 import { envMock } from '../../../test/exec-util';
 import { mocked } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import * as _env from '../exec/env';
 import {
   ensureCacheDir,
@@ -22,7 +22,7 @@ const env = mocked(_env);
 describe('util/fs/index', () => {
   describe('readLocalFile', () => {
     beforeEach(() => {
-      setGlobalConfig({ localDir: '' });
+      GlobalConfig.set({ localDir: '' });
     });
 
     it('reads buffer', async () => {
@@ -56,7 +56,7 @@ describe('util/fs/index', () => {
     it('returns path for file', async () => {
       await withDir(
         async (localDir) => {
-          setGlobalConfig({
+          GlobalConfig.set({
             localDir: localDir.path,
           });
 
@@ -110,7 +110,7 @@ describe('util/fs/index', () => {
     it('returns dir content', async () => {
       await withDir(
         async (localDir) => {
-          setGlobalConfig({
+          GlobalConfig.set({
             localDir: localDir.path,
           });
           await writeLocalFile('test/Cargo.toml', '');
@@ -138,7 +138,7 @@ describe('util/fs/index', () => {
     it('return empty array for non existing directory', async () => {
       await withDir(
         async (localDir) => {
-          setGlobalConfig({
+          GlobalConfig.set({
             localDir: localDir.path,
           });
           await expect(readLocalDirectory('somedir')).rejects.toThrow();
@@ -170,7 +170,7 @@ describe('util/fs/index', () => {
         ...envMock.basic,
       });
 
-      setGlobalConfig({
+      GlobalConfig.set({
         cacheDir: join(dirFromConfig),
       });
 
diff --git a/lib/util/fs/index.ts b/lib/util/fs/index.ts
index 03e2d6be35..eae38c5237 100644
--- a/lib/util/fs/index.ts
+++ b/lib/util/fs/index.ts
@@ -3,7 +3,7 @@ import util from 'util';
 import is from '@sindresorhus/is';
 import * as fs from 'fs-extra';
 import { isAbsolute, join, parse } from 'upath';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { logger } from '../../logger';
 
 export * from './proxies';
@@ -31,7 +31,7 @@ export async function readLocalFile(
   fileName: string,
   encoding?: string
 ): Promise<string | Buffer> {
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   const localFileName = join(localDir, fileName);
   try {
     const fileContent = await fs.readFile(localFileName, encoding);
@@ -46,13 +46,13 @@ export async function writeLocalFile(
   fileName: string,
   fileContent: string
 ): Promise<void> {
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   const localFileName = join(localDir, fileName);
   await fs.outputFile(localFileName, fileContent);
 }
 
 export async function deleteLocalFile(fileName: string): Promise<void> {
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   if (localDir) {
     const localFileName = join(localDir, fileName);
     await fs.remove(localFileName);
@@ -64,7 +64,7 @@ export async function renameLocalFile(
   fromFile: string,
   toFile: string
 ): Promise<void> {
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   await fs.move(join(localDir, fromFile), join(localDir, toFile));
 }
 
@@ -77,13 +77,13 @@ export async function ensureDir(dirName: string): Promise<void> {
 
 // istanbul ignore next
 export async function ensureLocalDir(dirName: string): Promise<void> {
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   const localDirName = join(localDir, dirName);
   await fs.ensureDir(localDirName);
 }
 
 export async function ensureCacheDir(name: string): Promise<string> {
-  const cacheDirName = join(getGlobalConfig().cacheDir, `others/${name}`);
+  const cacheDirName = join(GlobalConfig.get('cacheDir'), `others/${name}`);
   await fs.ensureDir(cacheDirName);
   return cacheDirName;
 }
@@ -94,12 +94,12 @@ export async function ensureCacheDir(name: string): Promise<string> {
  * without risk of that information leaking to other repositories/users.
  */
 export function privateCacheDir(): string {
-  const { cacheDir } = getGlobalConfig();
+  const { cacheDir } = GlobalConfig.get();
   return join(cacheDir, '__renovate-private-cache');
 }
 
 export function localPathExists(pathName: string): Promise<boolean> {
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   // Works for both files as well as directories
   return fs
     .stat(join(localDir, pathName))
@@ -140,7 +140,7 @@ export async function findLocalSiblingOrParent(
  * Get files by name from directory
  */
 export async function readLocalDirectory(path: string): Promise<string[]> {
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   const localPath = join(localDir, path);
   const fileList = await fs.readdir(localPath);
   return fileList;
diff --git a/lib/util/git/index.spec.ts b/lib/util/git/index.spec.ts
index 83aff7d3e2..99e2ec2214 100644
--- a/lib/util/git/index.spec.ts
+++ b/lib/util/git/index.spec.ts
@@ -2,7 +2,7 @@ import fs from 'fs-extra';
 import Git from 'simple-git';
 import SimpleGit from 'simple-git/src/git';
 import tmp from 'tmp-promise';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { CONFIG_VALIDATION } from '../../constants/error-messages';
 import * as git from '.';
 import { GitNoVerifyOption, setNoVerify } from '.';
@@ -71,7 +71,7 @@ describe('util/git/index', () => {
     await repo.clone(base.path, '.', ['--bare']);
     await repo.addConfig('commit.gpgsign', 'false');
     tmpDir = await tmp.dir({ unsafeCleanup: true });
-    setGlobalConfig({ localDir: tmpDir.path });
+    GlobalConfig.set({ localDir: tmpDir.path });
     await git.initRepo({
       url: origin.path,
     });
diff --git a/lib/util/git/index.ts b/lib/util/git/index.ts
index a79522eb5d..fcd7990b5f 100644
--- a/lib/util/git/index.ts
+++ b/lib/util/git/index.ts
@@ -10,7 +10,7 @@ import Git, {
 } from 'simple-git';
 import { join } from 'upath';
 import { configFileNames } from '../../config/app-strings';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RenovateConfig } from '../../config/types';
 import {
   CONFIG_VALIDATION,
@@ -199,7 +199,7 @@ export async function initRepo(args: StorageConfig): Promise<void> {
   config.ignoredAuthors = [];
   config.additionalBranches = [];
   config.branchIsModified = {};
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   git = Git(localDir, simpleGitConfig());
   gitInitialized = false;
   await fetchBranchCommits();
@@ -298,7 +298,7 @@ export async function syncGit(): Promise<void> {
     return;
   }
   gitInitialized = true;
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   logger.debug('Initializing git repository into ' + localDir);
   const gitHead = join(localDir, '.git/HEAD');
   let clone = true;
@@ -699,7 +699,7 @@ export async function commitFiles({
     await writePrivateKey();
     privateKeySet = true;
   }
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   await configSigningKey(localDir);
   await writeGitAuthor();
   try {
diff --git a/lib/util/json-writer/editor-config.spec.ts b/lib/util/json-writer/editor-config.spec.ts
index b3bbe22780..068028334d 100644
--- a/lib/util/json-writer/editor-config.spec.ts
+++ b/lib/util/json-writer/editor-config.spec.ts
@@ -1,7 +1,7 @@
 import mockFs from 'mock-fs';
 import { loadFixture } from '../../../test/util';
 import { configFileNames } from '../../config/app-strings';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { EditorConfig } from './editor-config';
 import { IndentationType } from './indentation-type';
 
@@ -13,7 +13,7 @@ const NON_JSON_FILES_EDITOR_CONFIG = loadFixture('.non_json_editorconfig', '.');
 
 describe('util/json-writer/editor-config', () => {
   beforeAll(() => {
-    setGlobalConfig({
+    GlobalConfig.set({
       localDir: '',
     });
   });
diff --git a/lib/util/json-writer/editor-config.ts b/lib/util/json-writer/editor-config.ts
index 33bc638642..f025d02d13 100644
--- a/lib/util/json-writer/editor-config.ts
+++ b/lib/util/json-writer/editor-config.ts
@@ -1,12 +1,12 @@
 import { KnownProps, parse } from 'editorconfig';
 import { join } from 'upath';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { CodeFormat } from './code-format';
 import { IndentationType } from './indentation-type';
 
 export class EditorConfig {
   public static async getCodeFormat(fileName: string): Promise<CodeFormat> {
-    const { localDir } = getGlobalConfig();
+    const { localDir } = GlobalConfig.get();
     const knownProps = await parse(join(localDir, fileName));
 
     return {
diff --git a/lib/util/template/index.ts b/lib/util/template/index.ts
index 4a4ad1ee33..a4e84d6025 100644
--- a/lib/util/template/index.ts
+++ b/lib/util/template/index.ts
@@ -1,6 +1,6 @@
 import is from '@sindresorhus/is';
 import * as handlebars from 'handlebars';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { logger } from '../../logger';
 import { clone } from '../clone';
 
@@ -154,7 +154,7 @@ export function compile(
   input: CompileInput,
   filterFields = true
 ): string {
-  const data = { ...getGlobalConfig(), ...input };
+  const data = { ...GlobalConfig.get(), ...input };
   const filteredInput = filterFields ? getFilteredObject(data) : data;
   logger.trace({ template, filteredInput }, 'Compiling template');
   if (filterFields) {
diff --git a/lib/workers/branch/artifacts.spec.ts b/lib/workers/branch/artifacts.spec.ts
index d185d13c06..d234cd7867 100644
--- a/lib/workers/branch/artifacts.spec.ts
+++ b/lib/workers/branch/artifacts.spec.ts
@@ -1,5 +1,5 @@
 import { getConfig, platform } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { BranchStatus } from '../../types';
 import { BranchConfig } from '../types';
 import { setArtifactErrorStatus } from './artifacts';
@@ -7,7 +7,7 @@ import { setArtifactErrorStatus } from './artifacts';
 describe('workers/branch/artifacts', () => {
   let config: BranchConfig;
   beforeEach(() => {
-    setGlobalConfig({});
+    GlobalConfig.set({});
     jest.resetAllMocks();
     config = {
       ...getConfig(),
@@ -31,7 +31,7 @@ describe('workers/branch/artifacts', () => {
     });
 
     it('skips status (dry-run)', async () => {
-      setGlobalConfig({ dryRun: true });
+      GlobalConfig.set({ dryRun: true });
       platform.getBranchStatusCheck.mockResolvedValueOnce(null);
       await setArtifactErrorStatus(config);
       expect(platform.setBranchStatus).not.toHaveBeenCalled();
diff --git a/lib/workers/branch/artifacts.ts b/lib/workers/branch/artifacts.ts
index 6c982bb99c..8a82b19184 100644
--- a/lib/workers/branch/artifacts.ts
+++ b/lib/workers/branch/artifacts.ts
@@ -1,4 +1,4 @@
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { logger } from '../../logger';
 import { platform } from '../../platform';
 import { BranchStatus } from '../../types';
@@ -23,7 +23,7 @@ export async function setArtifactErrorStatus(
   // Check if state needs setting
   if (existingState !== state) {
     logger.debug(`Updating status check state to failed`);
-    if (getGlobalConfig().dryRun) {
+    if (GlobalConfig.get('dryRun')) {
       logger.info('DRY-RUN: Would set branch status in ' + config.branchName);
     } else {
       await platform.setBranchStatus({
diff --git a/lib/workers/branch/automerge.spec.ts b/lib/workers/branch/automerge.spec.ts
index 9b6439f4fe..65de824a3e 100644
--- a/lib/workers/branch/automerge.spec.ts
+++ b/lib/workers/branch/automerge.spec.ts
@@ -1,5 +1,5 @@
 import { defaultConfig, git, platform } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RenovateConfig } from '../../config/types';
 import { BranchStatus } from '../../types';
 import { tryBranchAutomerge } from './automerge';
@@ -13,7 +13,7 @@ describe('workers/branch/automerge', () => {
       config = {
         ...defaultConfig,
       };
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
     it('returns false if not configured for automerge', async () => {
       config.automerge = false;
@@ -63,7 +63,7 @@ describe('workers/branch/automerge', () => {
     it('returns true if automerge succeeds (dry-run)', async () => {
       config.automerge = true;
       config.automergeType = 'branch';
-      setGlobalConfig({ dryRun: true });
+      GlobalConfig.set({ dryRun: true });
       platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green);
       expect(await tryBranchAutomerge(config)).toBe('automerged');
     });
diff --git a/lib/workers/branch/automerge.ts b/lib/workers/branch/automerge.ts
index fd4f82b350..c2d53a1fc5 100644
--- a/lib/workers/branch/automerge.ts
+++ b/lib/workers/branch/automerge.ts
@@ -1,4 +1,4 @@
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RenovateConfig } from '../../config/types';
 import { logger } from '../../logger';
 import { platform } from '../../platform';
@@ -33,7 +33,7 @@ export async function tryBranchAutomerge(
   if (branchStatus === BranchStatus.green) {
     logger.debug(`Automerging branch`);
     try {
-      if (getGlobalConfig().dryRun) {
+      if (GlobalConfig.get('dryRun')) {
         logger.info('DRY-RUN: Would automerge branch' + config.branchName);
       } else {
         await mergeBranch(config.branchName);
diff --git a/lib/workers/branch/commit.spec.ts b/lib/workers/branch/commit.spec.ts
index 6a99afd89c..c501a90c29 100644
--- a/lib/workers/branch/commit.spec.ts
+++ b/lib/workers/branch/commit.spec.ts
@@ -1,5 +1,5 @@
 import { defaultConfig, git, partial } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { BranchConfig } from '../types';
 import { commitFilesToBranch } from './commit';
 
@@ -21,7 +21,7 @@ describe('workers/branch/commit', () => {
       });
       jest.resetAllMocks();
       git.commitFiles.mockResolvedValueOnce('123test');
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
     it('handles empty files', async () => {
       await commitFilesToBranch(config);
@@ -37,7 +37,7 @@ describe('workers/branch/commit', () => {
       expect(git.commitFiles.mock.calls).toMatchSnapshot();
     });
     it('dry runs', async () => {
-      setGlobalConfig({ dryRun: true });
+      GlobalConfig.set({ dryRun: true });
       config.updatedPackageFiles.push({
         name: 'package.json',
         contents: 'some contents',
diff --git a/lib/workers/branch/commit.ts b/lib/workers/branch/commit.ts
index 917578f569..bd201e7ea8 100644
--- a/lib/workers/branch/commit.ts
+++ b/lib/workers/branch/commit.ts
@@ -1,6 +1,6 @@
 import is from '@sindresorhus/is';
 import minimatch from 'minimatch';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { CONFIG_SECRETS_EXPOSED } from '../../constants/error-messages';
 import { logger } from '../../logger';
 import { commitFiles } from '../../util/git';
@@ -32,7 +32,7 @@ export function commitFilesToBranch(
   const fileLength = [...new Set(updatedFiles.map((file) => file.name))].length;
   logger.debug(`${fileLength} file(s) to commit`);
   // istanbul ignore if
-  if (getGlobalConfig().dryRun) {
+  if (GlobalConfig.get('dryRun')) {
     logger.info('DRY-RUN: Would commit files to branch ' + config.branchName);
     return null;
   }
diff --git a/lib/workers/branch/execute-post-upgrade-commands.ts b/lib/workers/branch/execute-post-upgrade-commands.ts
index 94cb192fa6..7c863533f9 100644
--- a/lib/workers/branch/execute-post-upgrade-commands.ts
+++ b/lib/workers/branch/execute-post-upgrade-commands.ts
@@ -1,6 +1,6 @@
 import is from '@sindresorhus/is';
 import minimatch from 'minimatch';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { addMeta, logger } from '../../logger';
 import type { ArtifactError } from '../../manager/types';
 import { exec } from '../../util/exec';
@@ -23,7 +23,7 @@ export async function postUpgradeCommandsExecutor(
   let updatedArtifacts = [...(config.updatedArtifacts || [])];
   const artifactErrors = [...(config.artifactErrors || [])];
   const { allowedPostUpgradeCommands, allowPostUpgradeCommandTemplating } =
-    getGlobalConfig();
+    GlobalConfig.get();
 
   for (const upgrade of filteredUpgradeCommands) {
     addMeta({ dep: upgrade.depName });
@@ -62,7 +62,7 @@ export async function postUpgradeCommandsExecutor(
 
             logger.debug({ cmd: compiledCmd }, 'Executing post-upgrade task');
             const execResult = await exec(compiledCmd, {
-              cwd: getGlobalConfig().localDir,
+              cwd: GlobalConfig.get('localDir'),
             });
 
             logger.debug(
@@ -149,7 +149,7 @@ export async function postUpgradeCommandsExecutor(
 export default async function executePostUpgradeCommands(
   config: BranchConfig
 ): Promise<PostUpgradeCommandsExecutionResult | null> {
-  const { allowedPostUpgradeCommands } = getGlobalConfig();
+  const { allowedPostUpgradeCommands } = GlobalConfig.get();
 
   const hasChangedFiles =
     config.updatedPackageFiles?.length > 0 ||
diff --git a/lib/workers/branch/handle-existing.ts b/lib/workers/branch/handle-existing.ts
index 969049e641..e0d00108fe 100644
--- a/lib/workers/branch/handle-existing.ts
+++ b/lib/workers/branch/handle-existing.ts
@@ -1,4 +1,4 @@
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { logger } from '../../logger';
 import { Pr, platform } from '../../platform';
 import { PrState } from '../../types';
@@ -19,7 +19,7 @@ export async function handlepr(config: BranchConfig, pr: Pr): Promise<void> {
       '\n\nIf this PR was closed by mistake or you changed your mind, you can simply rename this PR and you will soon get a fresh replacement PR opened.';
     if (!config.suppressNotifications.includes('prIgnoreNotification')) {
       const ignoreTopic = `Renovate Ignore Notification`;
-      if (getGlobalConfig().dryRun) {
+      if (GlobalConfig.get('dryRun')) {
         logger.info(
           `DRY-RUN: Would ensure closed PR comment in PR #${pr.number}`
         );
@@ -32,7 +32,7 @@ export async function handlepr(config: BranchConfig, pr: Pr): Promise<void> {
       }
     }
     if (branchExists(config.branchName)) {
-      if (getGlobalConfig().dryRun) {
+      if (GlobalConfig.get('dryRun')) {
         logger.info('DRY-RUN: Would delete branch ' + config.branchName);
       } else {
         await deleteBranch(config.branchName);
diff --git a/lib/workers/branch/index.spec.ts b/lib/workers/branch/index.spec.ts
index 07231792f9..d819e2e03b 100644
--- a/lib/workers/branch/index.spec.ts
+++ b/lib/workers/branch/index.spec.ts
@@ -1,6 +1,6 @@
 import * as _fs from 'fs-extra';
 import { defaultConfig, git, mocked, platform } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RepoGlobalConfig } from '../../config/types';
 import {
   MANAGER_LOCKFILE_ERROR,
@@ -93,7 +93,7 @@ describe('workers/branch/index', () => {
           body: '',
         },
       });
-      setGlobalConfig(adminConfig);
+      GlobalConfig.set(adminConfig);
       sanitize.sanitize.mockImplementation((input) => input);
     });
     afterEach(() => {
@@ -101,7 +101,7 @@ describe('workers/branch/index', () => {
       platform.ensureCommentRemoval.mockClear();
       commit.commitFilesToBranch.mockClear();
       jest.resetAllMocks();
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
     it('skips branch if not scheduled and branch does not exist', async () => {
       schedule.isScheduledNow.mockReturnValueOnce(false);
@@ -427,7 +427,7 @@ describe('workers/branch/index', () => {
       git.branchExists.mockReturnValue(true);
       commit.commitFilesToBranch.mockResolvedValueOnce(null);
       automerge.tryBranchAutomerge.mockResolvedValueOnce('automerged');
-      setGlobalConfig({ ...adminConfig, dryRun: true });
+      GlobalConfig.set({ ...adminConfig, dryRun: true });
       await branchWorker.processBranch(config);
       expect(automerge.tryBranchAutomerge).toHaveBeenCalledTimes(1);
       expect(prWorker.ensurePr).toHaveBeenCalledTimes(0);
@@ -732,7 +732,7 @@ describe('workers/branch/index', () => {
       checkExisting.prAlreadyExisted.mockResolvedValueOnce({
         state: PrState.Closed,
       } as Pr);
-      setGlobalConfig({ ...adminConfig, dryRun: true });
+      GlobalConfig.set({ ...adminConfig, dryRun: true });
       // FIXME: explicit assert condition
       expect(await branchWorker.processBranch(config)).toMatchSnapshot();
     });
@@ -743,7 +743,7 @@ describe('workers/branch/index', () => {
         state: PrState.Open,
       } as Pr);
       git.isBranchModified.mockResolvedValueOnce(true);
-      setGlobalConfig({ ...adminConfig, dryRun: true });
+      GlobalConfig.set({ ...adminConfig, dryRun: true });
       // FIXME: explicit assert condition
       expect(await branchWorker.processBranch(config)).toMatchSnapshot();
     });
@@ -766,7 +766,7 @@ describe('workers/branch/index', () => {
       git.isBranchModified.mockResolvedValueOnce(true);
       schedule.isScheduledNow.mockReturnValueOnce(false);
       commit.commitFilesToBranch.mockResolvedValueOnce(null);
-      setGlobalConfig({ ...adminConfig, dryRun: true });
+      GlobalConfig.set({ ...adminConfig, dryRun: true });
       // FIXME: explicit assert condition
       expect(
         await branchWorker.processBranch({
@@ -799,7 +799,7 @@ describe('workers/branch/index', () => {
         pr: {},
       } as EnsurePrResult);
       commit.commitFilesToBranch.mockResolvedValueOnce(null);
-      setGlobalConfig({ ...adminConfig, dryRun: true });
+      GlobalConfig.set({ ...adminConfig, dryRun: true });
       // FIXME: explicit assert condition
       expect(
         await branchWorker.processBranch({
@@ -875,7 +875,7 @@ describe('workers/branch/index', () => {
       schedule.isScheduledNow.mockReturnValueOnce(false);
       commit.commitFilesToBranch.mockResolvedValueOnce(null);
 
-      setGlobalConfig({
+      GlobalConfig.set({
         ...adminConfig,
         allowedPostUpgradeCommands: ['^echo {{{versioning}}}$'],
         allowPostUpgradeCommandTemplating: true,
@@ -953,7 +953,7 @@ describe('workers/branch/index', () => {
       schedule.isScheduledNow.mockReturnValueOnce(false);
       commit.commitFilesToBranch.mockResolvedValueOnce(null);
 
-      setGlobalConfig({
+      GlobalConfig.set({
         ...adminConfig,
         allowedPostUpgradeCommands: ['^exit 1$'],
         allowPostUpgradeCommandTemplating: true,
@@ -1022,7 +1022,7 @@ describe('workers/branch/index', () => {
 
       schedule.isScheduledNow.mockReturnValueOnce(false);
       commit.commitFilesToBranch.mockResolvedValueOnce(null);
-      setGlobalConfig({
+      GlobalConfig.set({
         ...adminConfig,
         allowedPostUpgradeCommands: ['^echo {{{versioning}}}$'],
         allowPostUpgradeCommandTemplating: false,
@@ -1103,7 +1103,7 @@ describe('workers/branch/index', () => {
       schedule.isScheduledNow.mockReturnValueOnce(false);
       commit.commitFilesToBranch.mockResolvedValueOnce(null);
 
-      setGlobalConfig({
+      GlobalConfig.set({
         ...adminConfig,
         allowedPostUpgradeCommands: ['^echo {{{depName}}}$'],
         allowPostUpgradeCommandTemplating: true,
@@ -1238,7 +1238,7 @@ describe('workers/branch/index', () => {
       schedule.isScheduledNow.mockReturnValueOnce(false);
       commit.commitFilesToBranch.mockResolvedValueOnce(null);
 
-      setGlobalConfig({
+      GlobalConfig.set({
         ...adminConfig,
         allowedPostUpgradeCommands: ['^echo hardcoded-string$'],
         allowPostUpgradeCommandTemplating: true,
diff --git a/lib/workers/branch/index.ts b/lib/workers/branch/index.ts
index 56f107a55a..4173209ffa 100644
--- a/lib/workers/branch/index.ts
+++ b/lib/workers/branch/index.ts
@@ -1,5 +1,5 @@
 import { DateTime } from 'luxon';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RenovateConfig } from '../../config/types';
 import {
   CONFIG_VALIDATION,
@@ -418,7 +418,7 @@ export async function processBranch(
     } else if (config.updatedArtifacts?.length && branchPr) {
       // If there are artifacts, no errors, and an existing PR then ensure any artifacts error comment is removed
       // istanbul ignore if
-      if (getGlobalConfig().dryRun) {
+      if (GlobalConfig.get('dryRun')) {
         logger.info(
           `DRY-RUN: Would ensure comment removal in PR #${branchPr.number}`
         );
@@ -484,7 +484,7 @@ export async function processBranch(
       const mergeStatus = await tryBranchAutomerge(config);
       logger.debug(`mergeStatus=${mergeStatus}`);
       if (mergeStatus === 'automerged') {
-        if (getGlobalConfig().dryRun) {
+        if (GlobalConfig.get('dryRun')) {
           logger.info('DRY-RUN: Would delete branch' + config.branchName);
         } else {
           await deleteBranchSilently(config.branchName);
@@ -662,7 +662,7 @@ export async function processBranch(
             config.suppressNotifications.includes('lockFileErrors')
           )
         ) {
-          if (getGlobalConfig().dryRun) {
+          if (GlobalConfig.get('dryRun')) {
             logger.info(
               `DRY-RUN: Would ensure lock file error comment in PR #${pr.number}`
             );
diff --git a/lib/workers/branch/lock-files/index.spec.ts b/lib/workers/branch/lock-files/index.spec.ts
index f226dcec5a..6adeb1205d 100644
--- a/lib/workers/branch/lock-files/index.spec.ts
+++ b/lib/workers/branch/lock-files/index.spec.ts
@@ -1,6 +1,6 @@
 import { git, mocked } from '../../../../test/util';
 import { getConfig } from '../../../config/defaults';
-import { setGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import * as _lockFiles from '../../../manager/npm/post-update';
 import * as _lerna from '../../../manager/npm/post-update/lerna';
 import * as _npm from '../../../manager/npm/post-update/npm';
@@ -31,7 +31,7 @@ const { writeUpdatedPackageFiles, getAdditionalFiles } = lockFiles;
 describe('workers/branch/lock-files/index', () => {
   describe('writeUpdatedPackageFiles', () => {
     beforeEach(() => {
-      setGlobalConfig({
+      GlobalConfig.set({
         localDir: 'some-tmp-dir',
       });
       fs.outputFile = jest.fn();
@@ -70,7 +70,7 @@ describe('workers/branch/lock-files/index', () => {
   });
   describe('getAdditionalFiles', () => {
     beforeEach(() => {
-      setGlobalConfig({
+      GlobalConfig.set({
         localDir: 'some-tmp-dir',
       });
       git.getFile.mockResolvedValueOnce('some lock file contents');
diff --git a/lib/workers/branch/reuse.ts b/lib/workers/branch/reuse.ts
index 8d53647b3e..a114fd864f 100644
--- a/lib/workers/branch/reuse.ts
+++ b/lib/workers/branch/reuse.ts
@@ -1,4 +1,4 @@
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { logger } from '../../logger';
 import { platform } from '../../platform';
 import type { RangeStrategy } from '../../types';
@@ -36,7 +36,7 @@ export async function shouldReuseExistingBranch(
     if (pr.labels?.includes(config.rebaseLabel)) {
       logger.debug(`Manual rebase requested via PR labels for #${pr.number}`);
       // istanbul ignore if
-      if (getGlobalConfig().dryRun) {
+      if (GlobalConfig.get('dryRun')) {
         logger.info(
           `DRY-RUN: Would delete label ${config.rebaseLabel} from #${pr.number}`
         );
diff --git a/lib/workers/pr/automerge.ts b/lib/workers/pr/automerge.ts
index f96dd04b3d..a03f15886d 100644
--- a/lib/workers/pr/automerge.ts
+++ b/lib/workers/pr/automerge.ts
@@ -1,4 +1,4 @@
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { logger } from '../../logger';
 import { Pr, platform } from '../../platform';
 import { BranchStatus } from '../../types';
@@ -76,7 +76,7 @@ export async function checkAutoMerge(
   if (automergeType === 'pr-comment') {
     logger.debug(`Applying automerge comment: ${automergeComment}`);
     // istanbul ignore if
-    if (getGlobalConfig().dryRun) {
+    if (GlobalConfig.get('dryRun')) {
       logger.info(
         `DRY-RUN: Would add PR automerge comment to PR #${pr.number}`
       );
@@ -100,7 +100,7 @@ export async function checkAutoMerge(
   }
   // Let's merge this
   // istanbul ignore if
-  if (getGlobalConfig().dryRun) {
+  if (GlobalConfig.get('dryRun')) {
     logger.info(
       `DRY-RUN: Would merge PR #${pr.number} with strategy "${automergeStrategy}"`
     );
diff --git a/lib/workers/pr/index.ts b/lib/workers/pr/index.ts
index 1b50dcbd79..c6b7898605 100644
--- a/lib/workers/pr/index.ts
+++ b/lib/workers/pr/index.ts
@@ -1,4 +1,4 @@
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RenovateConfig } from '../../config/types';
 import {
   PLATFORM_INTEGRATION_UNAUTHORIZED,
@@ -70,7 +70,7 @@ export async function addAssigneesReviewers(
       }
       if (assignees.length > 0) {
         // istanbul ignore if
-        if (getGlobalConfig().dryRun) {
+        if (GlobalConfig.get('dryRun')) {
           logger.info(`DRY-RUN: Would add assignees to PR #${pr.number}`);
         } else {
           await platform.addAssignees(pr.number, assignees);
@@ -99,7 +99,7 @@ export async function addAssigneesReviewers(
       }
       if (reviewers.length > 0) {
         // istanbul ignore if
-        if (getGlobalConfig().dryRun) {
+        if (GlobalConfig.get('dryRun')) {
           logger.info(`DRY-RUN: Would add reviewers to PR #${pr.number}`);
         } else {
           await platform.addReviewers(pr.number, reviewers);
@@ -378,7 +378,7 @@ export async function ensurePr(
         );
       }
       // istanbul ignore if
-      if (getGlobalConfig().dryRun) {
+      if (GlobalConfig.get('dryRun')) {
         logger.info(`DRY-RUN: Would update PR #${existingPr.number}`);
       } else {
         await platform.updatePr({
@@ -401,7 +401,7 @@ export async function ensurePr(
     let pr: Pr;
     try {
       // istanbul ignore if
-      if (getGlobalConfig().dryRun) {
+      if (GlobalConfig.get('dryRun')) {
         logger.info('DRY-RUN: Would create PR: ' + prTitle);
         pr = { number: 0, displayNumber: 'Dry run PR' } as never;
       } else {
@@ -444,7 +444,7 @@ export async function ensurePr(
           { branch: branchName },
           'Deleting branch due to server error'
         );
-        if (getGlobalConfig().dryRun) {
+        if (GlobalConfig.get('dryRun')) {
           logger.info('DRY-RUN: Would delete branch: ' + config.branchName);
         } else {
           await deleteBranch(branchName);
@@ -465,7 +465,7 @@ export async function ensurePr(
       content = platform.massageMarkdown(content);
       logger.debug('Adding branch automerge failure message to PR');
       // istanbul ignore if
-      if (getGlobalConfig().dryRun) {
+      if (GlobalConfig.get('dryRun')) {
         logger.info(`DRY-RUN: Would add comment to PR #${pr.number}`);
       } else {
         await platform.ensureComment({
diff --git a/lib/workers/repository/dependency-dashboard.spec.ts b/lib/workers/repository/dependency-dashboard.spec.ts
index 2b12e65184..94b4edd93a 100644
--- a/lib/workers/repository/dependency-dashboard.spec.ts
+++ b/lib/workers/repository/dependency-dashboard.spec.ts
@@ -7,7 +7,7 @@ import {
   logger,
   platform,
 } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { PlatformId } from '../../constants';
 import type { Platform } from '../../platform';
 import { BranchConfig, BranchResult, BranchUpgradeConfig } from '../types';
@@ -32,7 +32,7 @@ async function dryRun(
   ensureIssueCalls = 0
 ) {
   jest.clearAllMocks();
-  setGlobalConfig({ dryRun: true });
+  GlobalConfig.set({ dryRun: true });
   await dependencyDashboard.ensureDependencyDashboard(config, branches);
   expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(
     ensureIssueClosingCalls
@@ -67,7 +67,7 @@ describe('workers/repository/dependency-dashboard', () => {
 
   describe('ensureDependencyDashboard()', () => {
     beforeEach(() => {
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
     it('do nothing if dependencyDashboard is disabled', async () => {
       const branches: BranchConfig[] = [];
diff --git a/lib/workers/repository/dependency-dashboard.ts b/lib/workers/repository/dependency-dashboard.ts
index 6d1e9404e5..ce2dfea6f2 100644
--- a/lib/workers/repository/dependency-dashboard.ts
+++ b/lib/workers/repository/dependency-dashboard.ts
@@ -1,6 +1,6 @@
 import is from '@sindresorhus/is';
 import { nameFromLevel } from 'bunyan';
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RenovateConfig } from '../../config/types';
 import { getProblems, logger } from '../../logger';
 import { platform } from '../../platform';
@@ -111,7 +111,7 @@ export async function ensureDependencyDashboard(
       )
     )
   ) {
-    if (getGlobalConfig().dryRun) {
+    if (GlobalConfig.get('dryRun')) {
       logger.info(
         { title: config.dependencyDashboardTitle },
         'DRY-RUN: Would close Dependency Dashboard'
@@ -132,7 +132,7 @@ export async function ensureDependencyDashboard(
     is.nonEmptyArray(branches) &&
     branches.some((branch) => branch.result !== BranchResult.Automerged);
   if (config.dependencyDashboardAutoclose && !hasBranches) {
-    if (getGlobalConfig().dryRun) {
+    if (GlobalConfig.get('dryRun')) {
       logger.info(
         { title: config.dependencyDashboardTitle },
         'DRY-RUN: Would close Dependency Dashboard'
@@ -345,7 +345,7 @@ export async function ensureDependencyDashboard(
     }
   }
 
-  if (getGlobalConfig().dryRun) {
+  if (GlobalConfig.get('dryRun')) {
     logger.info(
       { title: config.dependencyDashboardTitle },
       'DRY-RUN: Would ensure Dependency Dashboard'
diff --git a/lib/workers/repository/error-config.spec.ts b/lib/workers/repository/error-config.spec.ts
index 6f791725ce..f940702b30 100644
--- a/lib/workers/repository/error-config.spec.ts
+++ b/lib/workers/repository/error-config.spec.ts
@@ -1,6 +1,6 @@
 import { mock } from 'jest-mock-extended';
 import { RenovateConfig, getConfig, platform } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import { CONFIG_VALIDATION } from '../../constants/error-messages';
 import { Pr } from '../../platform';
 import { PrState } from '../../types';
@@ -17,7 +17,7 @@ beforeEach(() => {
 describe('workers/repository/error-config', () => {
   describe('raiseConfigWarningIssue()', () => {
     beforeEach(() => {
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
     it('creates issues', async () => {
       const error = new Error(CONFIG_VALIDATION);
@@ -32,7 +32,7 @@ describe('workers/repository/error-config', () => {
       error.validationSource = 'package.json';
       error.validationMessage = 'some-message';
       platform.ensureIssue.mockResolvedValueOnce('created');
-      setGlobalConfig({ dryRun: true });
+      GlobalConfig.set({ dryRun: true });
       const res = await raiseConfigWarningIssue(config, error);
       expect(res).toBeUndefined();
     });
@@ -57,7 +57,7 @@ describe('workers/repository/error-config', () => {
         number: 1,
         state: PrState.Open,
       });
-      setGlobalConfig({ dryRun: true });
+      GlobalConfig.set({ dryRun: true });
       const res = await raiseConfigWarningIssue(config, error);
       expect(res).toBeUndefined();
     });
diff --git a/lib/workers/repository/error-config.ts b/lib/workers/repository/error-config.ts
index 5b10967bac..c692ed4992 100644
--- a/lib/workers/repository/error-config.ts
+++ b/lib/workers/repository/error-config.ts
@@ -1,4 +1,4 @@
-import { getGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RenovateConfig } from '../../config/types';
 import { logger } from '../../logger';
 import { platform } from '../../platform';
@@ -26,7 +26,7 @@ export async function raiseConfigWarningIssue(
     logger.debug('Updating onboarding PR with config error notice');
     body = `## Action Required: Fix Renovate Configuration\n\n${body}`;
     body += `\n\nOnce you have resolved this problem (in this onboarding branch), Renovate will return to providing you with a preview of your repository's configuration.`;
-    if (getGlobalConfig().dryRun) {
+    if (GlobalConfig.get('dryRun')) {
       logger.info(`DRY-RUN: Would update PR #${pr.number}`);
     } else {
       try {
@@ -39,7 +39,7 @@ export async function raiseConfigWarningIssue(
         logger.warn({ err }, 'Error updating onboarding PR');
       }
     }
-  } else if (getGlobalConfig().dryRun) {
+  } else if (GlobalConfig.get('dryRun')) {
     logger.info('DRY-RUN: Would ensure config error issue');
   } else {
     const once = false;
diff --git a/lib/workers/repository/finalise/prune.spec.ts b/lib/workers/repository/finalise/prune.spec.ts
index 6e2c0c7418..a57909be0e 100644
--- a/lib/workers/repository/finalise/prune.spec.ts
+++ b/lib/workers/repository/finalise/prune.spec.ts
@@ -4,7 +4,7 @@ import {
   git,
   platform,
 } from '../../../../test/util';
-import { setGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import { PlatformId } from '../../../constants';
 import * as cleanup from './prune';
 
@@ -22,7 +22,7 @@ beforeEach(() => {
 describe('workers/repository/finalise/prune', () => {
   describe('pruneStaleBranches()', () => {
     beforeEach(() => {
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
     it('returns if no branchList', async () => {
       delete config.branchList;
@@ -69,7 +69,7 @@ describe('workers/repository/finalise/prune', () => {
     });
     it('does nothing on dryRun', async () => {
       config.branchList = ['renovate/a', 'renovate/b'];
-      setGlobalConfig({ dryRun: true });
+      GlobalConfig.set({ dryRun: true });
       git.getBranchList.mockReturnValueOnce(
         config.branchList.concat(['renovate/c'])
       );
@@ -109,7 +109,7 @@ describe('workers/repository/finalise/prune', () => {
     });
     it('skips comment if dry run', async () => {
       config.branchList = ['renovate/a', 'renovate/b'];
-      setGlobalConfig({ dryRun: true });
+      GlobalConfig.set({ dryRun: true });
       git.getBranchList.mockReturnValueOnce(
         config.branchList.concat(['renovate/c'])
       );
@@ -124,7 +124,7 @@ describe('workers/repository/finalise/prune', () => {
     });
     it('dry run delete branch no PR', async () => {
       config.branchList = ['renovate/a', 'renovate/b'];
-      setGlobalConfig({ dryRun: true });
+      GlobalConfig.set({ dryRun: true });
       git.getBranchList.mockReturnValueOnce(
         config.branchList.concat(['renovate/c'])
       );
diff --git a/lib/workers/repository/finalise/prune.ts b/lib/workers/repository/finalise/prune.ts
index 94cc51609d..17aaf3bd05 100644
--- a/lib/workers/repository/finalise/prune.ts
+++ b/lib/workers/repository/finalise/prune.ts
@@ -1,4 +1,4 @@
-import { getGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import type { RenovateConfig } from '../../../config/types';
 import { REPOSITORY_CHANGED } from '../../../constants/error-messages';
 import { logger } from '../../../logger';
@@ -31,7 +31,7 @@ async function cleanUpBranches(
             { prNo: pr.number, prTitle: pr.title },
             'Branch is modified - skipping PR autoclosing'
           );
-          if (getGlobalConfig().dryRun) {
+          if (GlobalConfig.get('dryRun')) {
             logger.info(`DRY-RUN: Would add Autoclosing Skipped comment to PR`);
           } else {
             await platform.ensureComment({
@@ -41,7 +41,7 @@ async function cleanUpBranches(
                 'This PR has been flagged for autoclosing, however it is being skipped due to the branch being already modified. Please close/delete it manually or report a bug if you think this is in error.',
             });
           }
-        } else if (getGlobalConfig().dryRun) {
+        } else if (GlobalConfig.get('dryRun')) {
           logger.info(
             { prNo: pr.number, prTitle: pr.title },
             `DRY-RUN: Would autoclose PR`
@@ -62,7 +62,7 @@ async function cleanUpBranches(
           });
           await deleteBranch(branchName);
         }
-      } else if (getGlobalConfig().dryRun) {
+      } else if (GlobalConfig.get('dryRun')) {
         logger.info(`DRY-RUN: Would delete orphan branch ${branchName}`);
       } else {
         logger.info({ branch: branchName }, `Deleting orphan branch`);
diff --git a/lib/workers/repository/index.spec.ts b/lib/workers/repository/index.spec.ts
index 212de3ee05..e45c1b78ef 100644
--- a/lib/workers/repository/index.spec.ts
+++ b/lib/workers/repository/index.spec.ts
@@ -1,7 +1,7 @@
 import { mock } from 'jest-mock-extended';
 
 import { RenovateConfig, getConfig, mocked } from '../../../test/util';
-import { setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import * as _process from './process';
 import { ExtractResult } from './process/extract-update';
 import { renovateRepository } from '.';
@@ -18,7 +18,7 @@ describe('workers/repository/index', () => {
     let config: RenovateConfig;
     beforeEach(() => {
       config = getConfig();
-      setGlobalConfig({ localDir: '' });
+      GlobalConfig.set({ localDir: '' });
     });
     it('runs', async () => {
       process.extractDependencies.mockResolvedValue(mock<ExtractResult>());
diff --git a/lib/workers/repository/index.ts b/lib/workers/repository/index.ts
index c658082f0f..1357f31655 100644
--- a/lib/workers/repository/index.ts
+++ b/lib/workers/repository/index.ts
@@ -1,5 +1,5 @@
 import fs from 'fs-extra';
-import { getGlobalConfig, setGlobalConfig } from '../../config/global';
+import { GlobalConfig } from '../../config/global';
 import type { RenovateConfig } from '../../config/types';
 import { logger, setMeta } from '../../logger';
 import { removeDanglingContainers } from '../../util/exec/docker';
@@ -30,14 +30,14 @@ export async function renovateRepository(
   canRetry = true
 ): Promise<ProcessResult> {
   splitInit();
-  let config = setGlobalConfig(repoConfig);
+  let config = GlobalConfig.set(repoConfig);
   await removeDanglingContainers();
   setMeta({ repository: config.repository });
   logger.info({ renovateVersion }, 'Repository started');
   logger.trace({ config });
   let repoResult: ProcessResult;
   queue.clear();
-  const { localDir } = getGlobalConfig();
+  const { localDir } = GlobalConfig.get();
   try {
     await fs.ensureDir(localDir);
     logger.debug('Using localDir: ' + localDir);
diff --git a/lib/workers/repository/init/cache.spec.ts b/lib/workers/repository/init/cache.spec.ts
index bb3de36699..9f06b635a1 100644
--- a/lib/workers/repository/init/cache.spec.ts
+++ b/lib/workers/repository/init/cache.spec.ts
@@ -1,5 +1,5 @@
 import { RenovateConfig, getConfig } from '../../../../test/util';
-import { setGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import { initializeCaches } from './cache';
 
 describe('workers/repository/init/cache', () => {
@@ -7,7 +7,7 @@ describe('workers/repository/init/cache', () => {
     let config: RenovateConfig;
     beforeEach(() => {
       config = { ...getConfig() };
-      setGlobalConfig({ cacheDir: '' });
+      GlobalConfig.set({ cacheDir: '' });
     });
     it('initializes', async () => {
       expect(await initializeCaches(config)).toBeUndefined();
diff --git a/lib/workers/repository/init/index.spec.ts b/lib/workers/repository/init/index.spec.ts
index d21970e174..a166c3d702 100644
--- a/lib/workers/repository/init/index.spec.ts
+++ b/lib/workers/repository/init/index.spec.ts
@@ -1,5 +1,5 @@
 import { logger, mocked } from '../../../../test/util';
-import { setGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import * as _secrets from '../../../config/secrets';
 import * as _onboarding from '../onboarding/branch';
 import * as _apis from './apis';
@@ -24,10 +24,10 @@ const secrets = mocked(_secrets);
 
 describe('workers/repository/init/index', () => {
   beforeEach(() => {
-    setGlobalConfig({ localDir: '', cacheDir: '' });
+    GlobalConfig.set({ localDir: '', cacheDir: '' });
   });
   afterEach(() => {
-    setGlobalConfig();
+    GlobalConfig.reset();
   });
 
   describe('initRepo', () => {
diff --git a/lib/workers/repository/onboarding/branch/config.spec.ts b/lib/workers/repository/onboarding/branch/config.spec.ts
index 7c9bc2e315..c5e62f9c56 100644
--- a/lib/workers/repository/onboarding/branch/config.spec.ts
+++ b/lib/workers/repository/onboarding/branch/config.spec.ts
@@ -1,5 +1,5 @@
 import { RenovateConfig, getConfig } from '../../../../../test/util';
-import { setGlobalConfig } from '../../../../config/global';
+import { GlobalConfig } from '../../../../config/global';
 import * as presets from '../../../../config/presets/local';
 import { PRESET_DEP_NOT_FOUND } from '../../../../config/presets/util';
 import { getOnboardingConfig, getOnboardingConfigContents } from './config';
@@ -12,7 +12,7 @@ describe('workers/repository/onboarding/branch/config', () => {
   let config: RenovateConfig;
 
   beforeAll(() => {
-    setGlobalConfig({
+    GlobalConfig.set({
       localDir: '',
     });
   });
diff --git a/lib/workers/repository/onboarding/branch/create.ts b/lib/workers/repository/onboarding/branch/create.ts
index a449e4d080..5b9bf89d24 100644
--- a/lib/workers/repository/onboarding/branch/create.ts
+++ b/lib/workers/repository/onboarding/branch/create.ts
@@ -1,5 +1,5 @@
 import { configFileNames } from '../../../../config/app-strings';
-import { getGlobalConfig } from '../../../../config/global';
+import { GlobalConfig } from '../../../../config/global';
 import type { RenovateConfig } from '../../../../config/types';
 import { logger } from '../../../../logger';
 import { commitFiles } from '../../../../util/git';
@@ -26,7 +26,7 @@ export async function createOnboardingBranch(
   const commitMessage = commitMessageFactory.create();
 
   // istanbul ignore if
-  if (getGlobalConfig().dryRun) {
+  if (GlobalConfig.get('dryRun')) {
     logger.info('DRY-RUN: Would commit files to onboarding branch');
     return null;
   }
diff --git a/lib/workers/repository/onboarding/branch/index.ts b/lib/workers/repository/onboarding/branch/index.ts
index 9910884ef0..26a453c781 100644
--- a/lib/workers/repository/onboarding/branch/index.ts
+++ b/lib/workers/repository/onboarding/branch/index.ts
@@ -1,5 +1,5 @@
 import { mergeChildConfig } from '../../../../config';
-import { getGlobalConfig } from '../../../../config/global';
+import { GlobalConfig } from '../../../../config/global';
 import type { RenovateConfig } from '../../../../config/types';
 import {
   REPOSITORY_FORKED,
@@ -70,7 +70,7 @@ export async function checkOnboardingBranch(
       );
     }
   }
-  if (!getGlobalConfig().dryRun) {
+  if (!GlobalConfig.get('dryRun')) {
     await checkoutBranch(onboardingBranch);
   }
   const branchList = [onboardingBranch];
diff --git a/lib/workers/repository/onboarding/branch/rebase.spec.ts b/lib/workers/repository/onboarding/branch/rebase.spec.ts
index c0d6e04898..2385799073 100644
--- a/lib/workers/repository/onboarding/branch/rebase.spec.ts
+++ b/lib/workers/repository/onboarding/branch/rebase.spec.ts
@@ -1,12 +1,12 @@
 import { RenovateConfig, defaultConfig, git } from '../../../../../test/util';
-import { setGlobalConfig } from '../../../../config/global';
+import { GlobalConfig } from '../../../../config/global';
 import { rebaseOnboardingBranch } from './rebase';
 
 jest.mock('../../../../util/git');
 
 describe('workers/repository/onboarding/branch/rebase', () => {
   beforeAll(() => {
-    setGlobalConfig({
+    GlobalConfig.set({
       localDir: '',
     });
   });
diff --git a/lib/workers/repository/onboarding/branch/rebase.ts b/lib/workers/repository/onboarding/branch/rebase.ts
index b905ebd984..a49c4eafd4 100644
--- a/lib/workers/repository/onboarding/branch/rebase.ts
+++ b/lib/workers/repository/onboarding/branch/rebase.ts
@@ -1,5 +1,5 @@
 import { configFileNames } from '../../../../config/app-strings';
-import { getGlobalConfig } from '../../../../config/global';
+import { GlobalConfig } from '../../../../config/global';
 import type { RenovateConfig } from '../../../../config/types';
 import { logger } from '../../../../logger';
 import {
@@ -43,7 +43,7 @@ export async function rebaseOnboardingBranch(
   const commitMessage = commitMessageFactory.create();
 
   // istanbul ignore if
-  if (getGlobalConfig().dryRun) {
+  if (GlobalConfig.get('dryRun')) {
     logger.info('DRY-RUN: Would rebase files in onboarding branch');
     return null;
   }
diff --git a/lib/workers/repository/onboarding/pr/index.spec.ts b/lib/workers/repository/onboarding/pr/index.spec.ts
index caa5e23b1f..debdb38980 100644
--- a/lib/workers/repository/onboarding/pr/index.spec.ts
+++ b/lib/workers/repository/onboarding/pr/index.spec.ts
@@ -5,7 +5,7 @@ import {
   partial,
   platform,
 } from '../../../../../test/util';
-import { setGlobalConfig } from '../../../../config/global';
+import { GlobalConfig } from '../../../../config/global';
 import { logger } from '../../../../logger';
 import type { PackageFile } from '../../../../manager/types';
 import { Pr } from '../../../../platform';
@@ -31,7 +31,7 @@ describe('workers/repository/onboarding/pr/index', () => {
       branches = [];
       platform.massageMarkdown = jest.fn((input) => input);
       platform.createPr.mockResolvedValueOnce(partial<Pr>({}));
-      setGlobalConfig();
+      GlobalConfig.reset();
     });
     let createPrBody: string;
     it('returns if onboarded', async () => {
@@ -89,7 +89,7 @@ describe('workers/repository/onboarding/pr/index', () => {
       expect(platform.createPr).toHaveBeenCalledTimes(1);
     });
     it('dryrun of updates PR when modified', async () => {
-      setGlobalConfig({ dryRun: true });
+      GlobalConfig.set({ dryRun: true });
       config.baseBranch = 'some-branch';
       platform.getBranchPr.mockResolvedValueOnce(
         partial<Pr>({
@@ -108,7 +108,7 @@ describe('workers/repository/onboarding/pr/index', () => {
       );
     });
     it('dryrun of creates PR', async () => {
-      setGlobalConfig({ dryRun: true });
+      GlobalConfig.set({ dryRun: true });
       await ensureOnboardingPr(config, packageFiles, branches);
       expect(logger.info).toHaveBeenCalledWith(
         'DRY-RUN: Would check branch renovate/configure'
diff --git a/lib/workers/repository/onboarding/pr/index.ts b/lib/workers/repository/onboarding/pr/index.ts
index 0f5ac5dcc4..bf0c7be77d 100644
--- a/lib/workers/repository/onboarding/pr/index.ts
+++ b/lib/workers/repository/onboarding/pr/index.ts
@@ -1,4 +1,4 @@
-import { getGlobalConfig } from '../../../../config/global';
+import { GlobalConfig } from '../../../../config/global';
 import type { RenovateConfig } from '../../../../config/types';
 import { logger } from '../../../../logger';
 import type { PackageFile } from '../../../../manager/types';
@@ -66,7 +66,7 @@ If you need any further assistance then you can also [request help here](${confi
     prBody = prBody.replace('{{PACKAGE FILES}}\n', '');
   }
   let configDesc = '';
-  if (getGlobalConfig().dryRun) {
+  if (GlobalConfig.get('dryRun')) {
     logger.info(`DRY-RUN: Would check branch ${config.onboardingBranch}`);
   } else if (await isBranchModified(config.onboardingBranch)) {
     configDesc = emojify(
@@ -114,7 +114,7 @@ If you need any further assistance then you can also [request help here](${confi
       return;
     }
     // PR must need updating
-    if (getGlobalConfig().dryRun) {
+    if (GlobalConfig.get('dryRun')) {
       logger.info('DRY-RUN: Would update onboarding PR');
     } else {
       await platform.updatePr({
@@ -129,7 +129,7 @@ If you need any further assistance then you can also [request help here](${confi
   logger.debug('Creating onboarding PR');
   const labels: string[] = config.addLabels ?? [];
   try {
-    if (getGlobalConfig().dryRun) {
+    if (GlobalConfig.get('dryRun')) {
       logger.info('DRY-RUN: Would create onboarding PR');
     } else {
       const pr = await platform.createPr({
diff --git a/lib/workers/repository/process/deprecated.ts b/lib/workers/repository/process/deprecated.ts
index 2c438d8394..53fdc99cf1 100644
--- a/lib/workers/repository/process/deprecated.ts
+++ b/lib/workers/repository/process/deprecated.ts
@@ -1,4 +1,4 @@
-import { getGlobalConfig } from '../../../config/global';
+import { GlobalConfig } from '../../../config/global';
 import type { RenovateConfig } from '../../../config/types';
 import { logger } from '../../../logger';
 import type { PackageFile } from '../../../manager/types';
@@ -55,7 +55,7 @@ export async function raiseDeprecationWarnings(
         .join(', ')}`;
       issueBody += `\n\nIf you don't care about this, you can close this issue and not be warned about \`${depName}\`'s deprecation again. If you would like to completely disable all future deprecation warnings then add the following to your config:\n\n\`\`\`\n"suppressNotifications": ["deprecationWarningIssues"]\n\`\`\`\n\n`;
       // istanbul ignore if
-      if (getGlobalConfig().dryRun) {
+      if (GlobalConfig.get('dryRun')) {
         logger.info('DRY-RUN: Ensure deprecation warning issue for ' + depName);
       } else {
         const ensureOnce = true;
-- 
GitLab