diff --git a/lib/manager/bundler/artifacts.ts b/lib/manager/bundler/artifacts.ts
index a5fc426078287109c72f838a12678a32129b84d3..dcf3389f3451b558879295eb35f6897cb9f22d6a 100644
--- a/lib/manager/bundler/artifacts.ts
+++ b/lib/manager/bundler/artifacts.ts
@@ -170,7 +170,6 @@ export async function updateArtifacts(
     }
 
     const cacheDir = await ensureCacheDir('./others/gem', 'GEM_HOME');
-    logger.debug(`Using gem home ${cacheDir}`);
 
     const execOptions: ExecOptions = {
       cwdFile: packageFileName,
diff --git a/lib/manager/cocoapods/artifacts.ts b/lib/manager/cocoapods/artifacts.ts
index fd63339dcff1106862e4fac62a3c1fcd62cd8ecc..1ac5905208ece16dc968bc17ae089f0be0c1b66a 100644
--- a/lib/manager/cocoapods/artifacts.ts
+++ b/lib/manager/cocoapods/artifacts.ts
@@ -68,7 +68,6 @@ export async function updateArtifacts({
   const tagConstraint = match?.groups?.cocoapodsVersion ?? null;
 
   const cacheDir = await ensureCacheDir('./others/cocoapods', 'CP_HOME_DIR');
-  logger.debug(`Using cocoapods home ${cacheDir}`);
 
   const cmd = [...getPluginCommands(newPackageFileContent), 'pod install'];
   const execOptions: ExecOptions = {
diff --git a/lib/manager/composer/artifacts.ts b/lib/manager/composer/artifacts.ts
index 1467e30e4c00e21e327d1f48ecca45c5c1f1aaa0..8d3f33afa62a3fb154c27fc9469c0de3bb1b6468 100644
--- a/lib/manager/composer/artifacts.ts
+++ b/lib/manager/composer/artifacts.ts
@@ -80,7 +80,6 @@ export async function updateArtifacts({
     './others/composer',
     'COMPOSER_CACHE_DIR'
   );
-  logger.debug(`Using composer cache ${cacheDir}`);
 
   const lockFileName = packageFileName.replace(/\.json$/, '.lock');
   const existingLockFileContent = await readLocalFile(lockFileName);
diff --git a/lib/manager/gomod/artifacts.ts b/lib/manager/gomod/artifacts.ts
index d76968ddf60293c501e9c3d544c5aead6d96a14e..8b64709cbbaa46fa7de1e03c51e856f3cf363ca1 100644
--- a/lib/manager/gomod/artifacts.ts
+++ b/lib/manager/gomod/artifacts.ts
@@ -93,7 +93,6 @@ export async function updateArtifacts({
   logger.debug(`gomod.updateArtifacts(${goModFileName})`);
 
   const goPath = await ensureCacheDir('./others/go', 'GOPATH');
-  logger.debug(`Using GOPATH: ${goPath}`);
 
   const sumFileName = goModFileName.replace(/\.mod$/, '.sum');
   const existingGoSumContent = await readLocalFile(sumFileName);
diff --git a/lib/manager/pipenv/artifacts.ts b/lib/manager/pipenv/artifacts.ts
index 1281f243667ae127c83d04494fe3fcb98df1401d..fa9e2a34771a7b5f12c552ce934e91b36e67d7b8 100644
--- a/lib/manager/pipenv/artifacts.ts
+++ b/lib/manager/pipenv/artifacts.ts
@@ -78,7 +78,6 @@ export async function updateArtifacts({
   logger.debug(`pipenv.updateArtifacts(${pipfileName})`);
 
   const cacheDir = await ensureCacheDir('./others/pipenv', 'PIPENV_CACHE_DIR');
-  logger.debug('Using pipenv cache ' + cacheDir);
 
   const lockFileName = pipfileName + '.lock';
   const existingLockFileContent = await readLocalFile(lockFileName, 'utf8');
diff --git a/lib/util/fs/index.spec.ts b/lib/util/fs/index.spec.ts
index e619dd32a5e3fbeb4c3eac6b9014db0c52fa9aa6..dbda45ea06e00da44e643ff159257d00f04615bf 100644
--- a/lib/util/fs/index.spec.ts
+++ b/lib/util/fs/index.spec.ts
@@ -1,8 +1,13 @@
 import { withDir } from 'tmp-promise';
-import { getName } from '../../../test/util';
+import { join } from 'upath';
+import { envMock } from '../../../test/exec-util';
+import { getName, mocked } from '../../../test/util';
 import { setAdminConfig } from '../../config/admin';
+import * as _env from '../exec/env';
 import {
+  ensureCacheDir,
   ensureLocalDir,
+  exists,
   findLocalSiblingOrParent,
   getSubDirectory,
   localPathExists,
@@ -11,6 +16,9 @@ import {
   writeLocalFile,
 } from '.';
 
+jest.mock('../../util/exec/env');
+const env = mocked(_env);
+
 describe(getName(), () => {
   describe('readLocalFile', () => {
     beforeEach(() => {
@@ -152,4 +160,73 @@ describe(getName(), () => {
       expect(result).toBeArrayOfSize(0);
     });
   });
+
+  describe('ensureCacheDir', () => {
+    function setupMock(root: string): {
+      dirFromEnv: string;
+      dirFromConfig: string;
+    } {
+      const dirFromEnv = join(root, join('/foo'));
+      const dirFromConfig = join(root, join('/bar'));
+
+      jest.resetAllMocks();
+      env.getChildProcessEnv.mockReturnValueOnce({
+        ...envMock.basic,
+        CUSTOM_CACHE_DIR: dirFromEnv,
+      });
+
+      setAdminConfig({
+        cacheDir: join(dirFromConfig),
+      });
+
+      return { dirFromEnv, dirFromConfig };
+    }
+
+    it('prefers environment variables over admin config', async () => {
+      await withDir(
+        async (tmpDir) => {
+          const { dirFromEnv, dirFromConfig } = setupMock(tmpDir.path);
+          const res = await ensureCacheDir(
+            './deeply/nested',
+            'CUSTOM_CACHE_DIR'
+          );
+          expect(res).toEqual(dirFromEnv);
+          expect(await exists(dirFromEnv)).toBeTrue();
+          expect(await exists(dirFromConfig)).toBeFalse();
+        },
+        { unsafeCleanup: true }
+      );
+    });
+
+    it('is optional to pass environment variable', async () => {
+      await withDir(
+        async (tmpDir) => {
+          const { dirFromEnv, dirFromConfig } = setupMock(tmpDir.path);
+          const expected = join(`${dirFromConfig}/deeply/nested`);
+          const res = await ensureCacheDir('./deeply/nested');
+          expect(res).toEqual(expected);
+          expect(await exists(expected)).toBeTrue();
+          expect(await exists(dirFromEnv)).toBeFalse();
+        },
+        { unsafeCleanup: true }
+      );
+    });
+
+    it('falls back to admin config', async () => {
+      await withDir(
+        async (tmpDir) => {
+          const { dirFromEnv, dirFromConfig } = setupMock(tmpDir.path);
+          const expected = join(`${dirFromConfig}/deeply/nested`);
+          const res = await ensureCacheDir(
+            './deeply/nested',
+            'NO_SUCH_VARIABLE'
+          );
+          expect(res).toEqual(expected);
+          expect(await exists(expected)).toBeTrue();
+          expect(await exists(dirFromEnv)).toBeFalse();
+        },
+        { unsafeCleanup: true }
+      );
+    });
+  });
 });
diff --git a/lib/util/fs/index.ts b/lib/util/fs/index.ts
index 8015dfdf850d1f93a154e4ce82120dc31f1aad57..054a267e1d95c042c218e9ce31e987d003cf0548 100644
--- a/lib/util/fs/index.ts
+++ b/lib/util/fs/index.ts
@@ -87,15 +87,26 @@ export async function ensureCacheDir(
   adminCacheSubdir: string,
   envCacheVar?: string
 ): Promise<string> {
-  const { cacheDir: adminCacheDir } = getAdminConfig();
-  let envCacheDir = null;
+  let cacheDir: string;
   if (envCacheVar) {
     const env = getChildProcessEnv([envCacheVar]);
-    envCacheDir = env[envCacheVar];
+    if (env[envCacheVar]) {
+      cacheDir = env[envCacheVar];
+      logger.debug(
+        { cacheDir },
+        `Using cache directory from environment: ${envCacheVar}`
+      );
+    }
+  }
+
+  if (!cacheDir) {
+    const { cacheDir: adminCacheDir } = getAdminConfig();
+    cacheDir = join(adminCacheDir, adminCacheSubdir);
+    logger.debug({ cacheDir }, `Using cache directory from admin config`);
   }
-  const cacheDirName = envCacheDir || join(adminCacheDir, adminCacheSubdir);
-  await fs.ensureDir(cacheDirName);
-  return cacheDirName;
+
+  await fs.ensureDir(cacheDir);
+  return cacheDir;
 }
 
 /**