diff --git a/lib/modules/manager/npm/post-update/__snapshots__/npm.spec.ts.snap b/lib/modules/manager/npm/post-update/__snapshots__/npm.spec.ts.snap
index df3d739b113d73ddac2a86281cee3669d24e5825..5e3efffe468929e50f151249575985bafc12ba80 100644
--- a/lib/modules/manager/npm/post-update/__snapshots__/npm.spec.ts.snap
+++ b/lib/modules/manager/npm/post-update/__snapshots__/npm.spec.ts.snap
@@ -1,9 +1,5 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`modules/manager/npm/post-update/npm catches errors 1`] = `Array []`;
-
-exports[`modules/manager/npm/post-update/npm finds npm globally 1`] = `Array []`;
-
 exports[`modules/manager/npm/post-update/npm generates lock files 1`] = `
 Array [
   Object {
@@ -45,8 +41,6 @@ Array [
 ]
 `;
 
-exports[`modules/manager/npm/post-update/npm performs full install 1`] = `Array []`;
-
 exports[`modules/manager/npm/post-update/npm performs lock file maintenance 1`] = `
 Array [
   Object {
@@ -202,9 +196,3 @@ Array [
   },
 ]
 `;
-
-exports[`modules/manager/npm/post-update/npm performs npm-shrinkwrap.json updates (no package-lock.json) 1`] = `Array []`;
-
-exports[`modules/manager/npm/post-update/npm performs npm-shrinkwrap.json updates 1`] = `Array []`;
-
-exports[`modules/manager/npm/post-update/npm uses docker npm 1`] = `Array []`;
diff --git a/lib/modules/manager/npm/post-update/node-version.spec.ts b/lib/modules/manager/npm/post-update/node-version.spec.ts
index fc222af12a04847a00e62572b909e3543fe7a110..b1953b75b2a960ceb9f7b7b2a966d63f31339875 100644
--- a/lib/modules/manager/npm/post-update/node-version.spec.ts
+++ b/lib/modules/manager/npm/post-update/node-version.spec.ts
@@ -1,5 +1,9 @@
 import { fs } from '../../../../../test/util';
-import { getNodeConstraint, getNodeUpdate } from './node-version';
+import {
+  getNodeConstraint,
+  getNodeToolConstraint,
+  getNodeUpdate,
+} from './node-version';
 
 jest.mock('../../../../util/fs');
 
@@ -56,4 +60,24 @@ describe('modules/manager/npm/post-update/node-version', () => {
       expect(getNodeUpdate([])).toBeUndefined();
     });
   });
+
+  describe('getNodeToolContraint()', () => {
+    it('returns getNodeUpdate', async () => {
+      expect(
+        await getNodeToolConstraint(config, [
+          { depName: 'node', newValue: '16.15.0' },
+        ])
+      ).toEqual({
+        toolName: 'node',
+        constraint: '16.15.0',
+      });
+    });
+
+    it('returns getNodeConstraint', async () => {
+      expect(await getNodeToolConstraint(config, [])).toEqual({
+        toolName: 'node',
+        constraint: '^12.16.0',
+      });
+    });
+  });
 });
diff --git a/lib/modules/manager/npm/post-update/node-version.ts b/lib/modules/manager/npm/post-update/node-version.ts
index b856a416512e6ec53eb2ff860e0fdc22c0db3676..6bf62de865f3f3ed9a6ef0e966de9a123c14696f 100644
--- a/lib/modules/manager/npm/post-update/node-version.ts
+++ b/lib/modules/manager/npm/post-update/node-version.ts
@@ -1,5 +1,6 @@
 import semver from 'semver';
 import { logger } from '../../../../logger';
+import type { ToolConstraint } from '../../../../util/exec/types';
 import { getSiblingFileName, readLocalFile } from '../../../../util/fs';
 import { newlineRegex, regEx } from '../../../../util/regex';
 import type { PostUpdateConfig, Upgrade } from '../../types';
@@ -35,9 +36,10 @@ export async function getNodeConstraint(
   config: Partial<PostUpdateConfig>
 ): Promise<string | null> {
   const { packageFile } = config;
+  // TODO: fix types (#7154)
   const constraint =
-    (await getNodeFile(getSiblingFileName(packageFile, '.nvmrc'))) ??
-    (await getNodeFile(getSiblingFileName(packageFile, '.node-version'))) ??
+    (await getNodeFile(getSiblingFileName(packageFile!, '.nvmrc'))) ??
+    (await getNodeFile(getSiblingFileName(packageFile!, '.node-version'))) ??
     getPackageJsonConstraint(config);
   if (!constraint) {
     logger.debug('No node constraint found - using latest');
@@ -48,3 +50,16 @@ export async function getNodeConstraint(
 export function getNodeUpdate(upgrades: Upgrade[]): string | undefined {
   return upgrades.find((u) => u.depName === 'node')?.newValue;
 }
+
+export async function getNodeToolConstraint(
+  config: Partial<PostUpdateConfig>,
+  upgrades: Upgrade[]
+): Promise<ToolConstraint> {
+  const constraint =
+    getNodeUpdate(upgrades) ?? (await getNodeConstraint(config));
+
+  return {
+    toolName: 'node',
+    constraint,
+  };
+}
diff --git a/lib/modules/manager/npm/post-update/npm.spec.ts b/lib/modules/manager/npm/post-update/npm.spec.ts
index 9c1d91e04a4821b7db256e6aee2920c2b19db1c7..6bdb012a7b115f213cb2a11709041866f31d3be9 100644
--- a/lib/modules/manager/npm/post-update/npm.spec.ts
+++ b/lib/modules/manager/npm/post-update/npm.spec.ts
@@ -1,20 +1,27 @@
 import upath from 'upath';
 import { envMock, mockExecAll } from '../../../../../test/exec-util';
 import { Fixtures } from '../../../../../test/fixtures';
-import { env, fs } from '../../../../../test/util';
+import { env, fs, mockedFunction } from '../../../../../test/util';
 import { GlobalConfig } from '../../../../config/global';
+import { getNodeToolConstraint } from './node-version';
 import * as npmHelper from './npm';
 
 jest.mock('../../../../util/exec/env');
 jest.mock('../../../../util/fs');
 jest.mock('./node-version');
 
+process.env.BUILDPACK = 'true';
+
 describe('modules/manager/npm/post-update/npm', () => {
   beforeEach(() => {
     jest.resetAllMocks();
     jest.resetModules();
     env.getChildProcessEnv.mockReturnValue(envMock.basic);
     GlobalConfig.set({ localDir: '' });
+    mockedFunction(getNodeToolConstraint).mockResolvedValueOnce({
+      toolName: 'node',
+      constraint: '16.16.0',
+    });
   });
 
   it('generates lock files', async () => {
@@ -109,7 +116,8 @@ describe('modules/manager/npm/post-update/npm', () => {
     );
     expect(res.error).toBeUndefined();
     expect(res.lockFile).toBe('package-lock-contents');
-    expect(execSnapshots).toMatchSnapshot();
+    // TODO: is that right?
+    expect(execSnapshots).toEqual([]);
   });
 
   it('performs npm-shrinkwrap.json updates (no package-lock.json)', async () => {
@@ -131,7 +139,8 @@ describe('modules/manager/npm/post-update/npm', () => {
     );
     expect(res.error).toBeUndefined();
     expect(res.lockFile).toBe('package-lock-contents');
-    expect(execSnapshots).toMatchSnapshot();
+    // TODO: is that right?
+    expect(execSnapshots).toEqual([]);
   });
 
   it('performs full install', async () => {
@@ -148,7 +157,8 @@ describe('modules/manager/npm/post-update/npm', () => {
     expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
     expect(res.error).toBeUndefined();
     expect(res.lockFile).toBe('package-lock-contents');
-    expect(execSnapshots).toMatchSnapshot();
+    // TODO: is that right?
+    expect(execSnapshots).toEqual([]);
   });
 
   it('runs twice if remediating', async () => {
@@ -181,7 +191,7 @@ describe('modules/manager/npm/post-update/npm', () => {
     expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
     expect(res.error).toBeTrue();
     expect(res.lockFile).toBeUndefined();
-    expect(execSnapshots).toMatchSnapshot();
+    expect(execSnapshots).toEqual([]);
   });
 
   it('finds npm globally', async () => {
@@ -194,7 +204,8 @@ describe('modules/manager/npm/post-update/npm', () => {
     );
     expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
     expect(res.lockFile).toBe('package-lock-contents');
-    expect(execSnapshots).toMatchSnapshot();
+    // TODO: is that right?
+    expect(execSnapshots).toEqual([]);
   });
 
   it('uses docker npm', async () => {
@@ -208,7 +219,8 @@ describe('modules/manager/npm/post-update/npm', () => {
     );
     expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
     expect(res.lockFile).toBe('package-lock-contents');
-    expect(execSnapshots).toMatchSnapshot();
+    // TODO: is that right?
+    expect(execSnapshots).toEqual([]);
   });
 
   it('performs lock file maintenance', async () => {
@@ -226,4 +238,69 @@ describe('modules/manager/npm/post-update/npm', () => {
     expect(res.lockFile).toBe('package-lock-contents');
     expect(execSnapshots).toMatchSnapshot();
   });
+
+  it('works for docker mode', async () => {
+    GlobalConfig.set({
+      localDir: '',
+      cacheDir: '/tmp',
+      binarySource: 'docker',
+      allowScripts: true,
+    });
+    const execSnapshots = mockExecAll();
+    fs.readLocalFile.mockResolvedValue('package-lock-contents');
+    const res = await npmHelper.generateLockFile(
+      'some-dir',
+      {},
+      'package-lock.json',
+      { constraints: { npm: '6.0.0' } },
+      [{ isLockFileMaintenance: true }]
+    );
+    expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
+    expect(res.lockFile).toBe('package-lock-contents');
+    expect(execSnapshots).toMatchObject([
+      { cmd: 'docker pull renovate/node' },
+      { cmd: 'docker ps --filter name=renovate_node -aq' },
+      {
+        cmd:
+          'docker run --rm --name=renovate_node --label=renovate_child ' +
+          '-v "/tmp":"/tmp" ' +
+          '-e BUILDPACK_CACHE_DIR ' +
+          '-w "some-dir" ' +
+          'renovate/node ' +
+          'bash -l -c "' +
+          'install-tool npm 6.0.0 ' +
+          '&& ' +
+          'hash -d npm 2>/dev/null || true ' +
+          '&& ' +
+          'npm install --package-lock-only --no-audit' +
+          '"',
+      },
+    ]);
+  });
+
+  it('works for install mode', async () => {
+    GlobalConfig.set({
+      localDir: '',
+      cacheDir: '/tmp',
+      binarySource: 'install',
+    });
+    const execSnapshots = mockExecAll();
+    fs.readLocalFile.mockResolvedValue('package-lock-contents');
+    const res = await npmHelper.generateLockFile(
+      'some-dir',
+      {},
+      'package-lock.json',
+      { constraints: { npm: '6.0.0' } },
+      [{ isLockFileMaintenance: true }]
+    );
+    expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
+    expect(res.lockFile).toBe('package-lock-contents');
+    expect(execSnapshots).toMatchObject([
+      { cmd: 'install-tool npm 6.0.0' },
+      { cmd: 'hash -d npm 2>/dev/null || true' },
+      {
+        cmd: 'npm install --package-lock-only --no-audit --ignore-scripts',
+      },
+    ]);
+  });
 });
diff --git a/lib/modules/manager/npm/post-update/pnpm.spec.ts b/lib/modules/manager/npm/post-update/pnpm.spec.ts
index 2924256993d8b78fbf3a7fa2dad62f1a8f095d12..c0ee55d699d708aa987d2b394308905425ff33ad 100644
--- a/lib/modules/manager/npm/post-update/pnpm.spec.ts
+++ b/lib/modules/manager/npm/post-update/pnpm.spec.ts
@@ -10,6 +10,7 @@ jest.mock('../../../../util/fs');
 jest.mock('./node-version');
 
 delete process.env.NPM_CONFIG_CACHE;
+process.env.BUILDPACK = 'true';
 
 describe('modules/manager/npm/post-update/pnpm', () => {
   let config: PostUpdateConfig;
@@ -178,4 +179,62 @@ describe('modules/manager/npm/post-update/pnpm', () => {
     expect(fs.readLocalFile).toHaveBeenCalledTimes(3);
     expect(res.lockFile).toBe('lockfileVersion: 5.3\n');
   });
+
+  it('works for docker mode', async () => {
+    GlobalConfig.set({
+      localDir: '',
+      cacheDir: '/tmp',
+      binarySource: 'docker',
+      allowScripts: true,
+    });
+    const execSnapshots = mockExecAll();
+    fs.readLocalFile.mockResolvedValue('package-lock-contents');
+    const res = await pnpmHelper.generateLockFile(
+      'some-dir',
+      {},
+      { ...config, constraints: { pnpm: '6.0.0' } }
+    );
+    expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
+    expect(res.lockFile).toBe('package-lock-contents');
+    expect(execSnapshots).toMatchObject([
+      { cmd: 'docker pull renovate/node' },
+      { cmd: 'docker ps --filter name=renovate_node -aq' },
+      {
+        cmd:
+          'docker run --rm --name=renovate_node --label=renovate_child ' +
+          '-v "/tmp":"/tmp" ' +
+          '-e BUILDPACK_CACHE_DIR ' +
+          '-w "some-dir" ' +
+          'renovate/node ' +
+          'bash -l -c "' +
+          'install-tool pnpm 6.0.0 ' +
+          '&& ' +
+          'pnpm install --recursive --lockfile-only' +
+          '"',
+      },
+    ]);
+  });
+
+  it('works for install mode', async () => {
+    GlobalConfig.set({
+      localDir: '',
+      cacheDir: '/tmp',
+      binarySource: 'install',
+    });
+    const execSnapshots = mockExecAll();
+    fs.readLocalFile.mockResolvedValue('package-lock-contents');
+    const res = await pnpmHelper.generateLockFile(
+      'some-dir',
+      {},
+      { ...config, constraints: { pnpm: '6.0.0' } }
+    );
+    expect(fs.readLocalFile).toHaveBeenCalledTimes(1);
+    expect(res.lockFile).toBe('package-lock-contents');
+    expect(execSnapshots).toMatchObject([
+      { cmd: 'install-tool pnpm 6.0.0' },
+      {
+        cmd: 'pnpm install --recursive --lockfile-only --ignore-scripts --ignore-pnpmfile',
+      },
+    ]);
+  });
 });
diff --git a/lib/modules/manager/types.ts b/lib/modules/manager/types.ts
index 38b81ff98041c09b45eed36b596c0731e180c39a..45797482dda734f044f2c79e6d7bc195ab30556b 100644
--- a/lib/modules/manager/types.ts
+++ b/lib/modules/manager/types.ts
@@ -288,6 +288,8 @@ export interface PostUpdateConfig<T = Record<string, any>>
   skipInstalls?: boolean;
   ignoreScripts?: boolean;
 
+  packageFile?: string;
+
   upgrades: Upgrade[];
   npmLock?: string;
   yarnLock?: string;
diff --git a/lib/util/exec/buildpack.spec.ts b/lib/util/exec/buildpack.spec.ts
index 7f489a182bd042da281fa2a45dc3bac5a7f3ff02..ca03191ac6bb7c32c7bd531e0f1bdd1e891100fc 100644
--- a/lib/util/exec/buildpack.spec.ts
+++ b/lib/util/exec/buildpack.spec.ts
@@ -33,12 +33,12 @@ describe('util/exec/buildpack', () => {
       process.env.BUILDPACK = 'true';
       const toolConstraints: ToolConstraint[] = [
         { toolName: 'node' },
-        { toolName: 'npm' },
+        { toolName: 'invalid' },
       ];
       expect(isDynamicInstall(toolConstraints)).toBeFalse();
     });
 
-    it('returns false if supported tools', () => {
+    it('returns true if supported tools', () => {
       GlobalConfig.set({ binarySource: 'install' });
       process.env.BUILDPACK = 'true';
       const toolConstraints: ToolConstraint[] = [{ toolName: 'npm' }];
diff --git a/lib/util/exec/buildpack.ts b/lib/util/exec/buildpack.ts
index 97b7e365e58d8376cca6179b51bf8749ebd1a26c..443fa167b526b205eb1666bfddbe844be1ba2651 100644
--- a/lib/util/exec/buildpack.ts
+++ b/lib/util/exec/buildpack.ts
@@ -63,6 +63,11 @@ const allToolConfig: Record<string, ToolConfig> = {
     depName: 'jsonnet-bundler/jsonnet-bundler',
     versioning: semverVersioningId,
   },
+  node: {
+    datasource: 'node',
+    depName: 'node',
+    versioning: npmVersioningId,
+  },
   npm: {
     datasource: 'npm',
     depName: 'npm',