diff --git a/lib/manager/composer/__snapshots__/artifacts.spec.ts.snap b/lib/manager/composer/__snapshots__/artifacts.spec.ts.snap
index 78bdf20c5043e9ba5cac76634d4f66647aaa1d09..7257baf1a935aa7f43fc6b706968cb32b0db7933 100644
--- a/lib/manager/composer/__snapshots__/artifacts.spec.ts.snap
+++ b/lib/manager/composer/__snapshots__/artifacts.spec.ts.snap
@@ -121,19 +121,19 @@ Array [
 exports[`.updateArtifacts() supports docker mode 1`] = `
 Array [
   Object {
-    "cmd": "docker pull renovate/composer:1.10.17",
+    "cmd": "docker pull renovate/php:7.3",
     "options": Object {
       "encoding": "utf-8",
     },
   },
   Object {
-    "cmd": "docker ps --filter name=renovate_composer -aq",
+    "cmd": "docker ps --filter name=renovate_php -aq",
     "options": Object {
       "encoding": "utf-8",
     },
   },
   Object {
-    "cmd": "docker run --rm --name=renovate_composer --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -e COMPOSER_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/composer:1.10.17 bash -l -c \\"composer update --with-dependencies --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins\\"",
+    "cmd": "docker run --rm --name=renovate_php --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -e COMPOSER_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/php:7.3 bash -l -c \\"install-tool composer 1.10.17 && composer update --with-dependencies --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins\\"",
     "options": Object {
       "cwd": "/tmp/github/some/repo",
       "encoding": "utf-8",
diff --git a/lib/manager/composer/artifacts.spec.ts b/lib/manager/composer/artifacts.spec.ts
index 012e7c345854e8b35e2b90e8d51015a4c7a6074a..25c3b7a56f198789106c163daa3f9ed7173eabc7 100644
--- a/lib/manager/composer/artifacts.spec.ts
+++ b/lib/manager/composer/artifacts.spec.ts
@@ -52,6 +52,18 @@ describe('.updateArtifacts()', () => {
     fs.ensureCacheDir.mockResolvedValue(
       join(adminConfig.cacheDir, './others/composer')
     );
+
+    datasource.getPkgReleases.mockResolvedValueOnce({
+      releases: [
+        { version: '1.0.0' },
+        { version: '1.1.0' },
+        { version: '1.3.0' },
+        { version: '1.10.0' },
+        { version: '1.10.17' },
+        { version: '2.0.14' },
+        { version: '2.1.0' },
+      ],
+    });
   });
 
   afterEach(() => {
@@ -214,12 +226,14 @@ describe('.updateArtifacts()', () => {
     const execSnapshots = mockExecAll(exec);
 
     fs.readLocalFile.mockResolvedValueOnce('{  }');
+
     datasource.getPkgReleases.mockResolvedValueOnce({
       releases: [
-        { version: '1.10.0' },
-        { version: '1.10.17' },
-        { version: '2.0.0' },
-        { version: '2.0.7' },
+        { version: '7.2.34' },
+        { version: '7.3' }, // composer versioning bug
+        { version: '7.3.29' },
+        { version: '7.4.22' },
+        { version: '8.0.6' },
       ],
     });
 
@@ -233,10 +247,11 @@ describe('.updateArtifacts()', () => {
         packageFileName: 'composer.json',
         updatedDeps: [],
         newPackageFileContent: '{}',
-        config: { ...config, constraints: { composer: '^1.10.0' } },
+        config: { ...config, constraints: { composer: '^1.10.0', php: '7.3' } },
       })
     ).not.toBeNull();
     expect(execSnapshots).toMatchSnapshot();
+    expect(execSnapshots).toHaveLength(3);
   });
 
   it('supports global mode', async () => {
diff --git a/lib/manager/composer/artifacts.ts b/lib/manager/composer/artifacts.ts
index 9beef4f7de72780e71bbc6472e684a963e9c596c..6f452b726e21ace97ce4909434acc9ba674b1501 100644
--- a/lib/manager/composer/artifacts.ts
+++ b/lib/manager/composer/artifacts.ts
@@ -28,7 +28,8 @@ import type { AuthJson } from './types';
 import {
   composerVersioningId,
   extractContraints,
-  getConstraint,
+  getComposerConstraint,
+  getPhpConstraint,
 } from './utils';
 
 function getAuthJson(): string | null {
@@ -110,6 +111,10 @@ export async function updateArtifacts({
       await deleteLocalFile(lockFileName);
     }
 
+    const preCommands: string[] = [
+      `install-tool composer ${await getComposerConstraint(constraints)}`,
+    ];
+
     const execOptions: ExecOptions = {
       cwdFile: packageFileName,
       extraEnv: {
@@ -117,8 +122,9 @@ export async function updateArtifacts({
         COMPOSER_AUTH: getAuthJson(),
       },
       docker: {
-        image: 'composer',
-        tagConstraint: getConstraint(constraints),
+        preCommands,
+        image: 'php',
+        tagConstraint: getPhpConstraint(constraints),
         tagScheme: composerVersioningId,
       },
     };
diff --git a/lib/manager/composer/utils.spec.ts b/lib/manager/composer/utils.spec.ts
index 6c488a0d0c85cb92cb55b070e42838a42aa4f7e0..8102e71fe88819af80332fc94f2e06eadce7212a 100644
--- a/lib/manager/composer/utils.spec.ts
+++ b/lib/manager/composer/utils.spec.ts
@@ -1,14 +1,52 @@
-import { getName } from '../../../test/util';
-import { extractContraints, getConstraint } from './utils';
+import { getName, mocked } from '../../../test/util';
+import * as _datasource from '../../datasource';
+import { extractContraints, getComposerConstraint } from './utils';
+
+jest.mock('../../../lib/datasource');
+
+const datasource = mocked(_datasource);
 
 describe(getName(), () => {
-  describe('getConstraint', () => {
-    it('returns from config', () => {
-      expect(getConstraint({ composer: '1.1.0' })).toEqual('1.1.0');
+  describe('getComposerConstraint', () => {
+    beforeEach(() => {
+      datasource.getPkgReleases.mockResolvedValueOnce({
+        releases: [
+          { version: '1.0.0' },
+          { version: '1.1.0' },
+          { version: '1.3.0' },
+          { version: '2.0.14' },
+          { version: '2.1.0' },
+        ],
+      });
+    });
+    it('returns from config', async () => {
+      expect(await getComposerConstraint({ composer: '1.1.0' })).toEqual(
+        '1.1.0'
+      );
+    });
+
+    it('returns from latest', async () => {
+      expect(await getComposerConstraint({})).toEqual('2.1.0');
     });
 
-    it('returns from null', () => {
-      expect(getConstraint({})).toBeNull();
+    it('throws no releases', async () => {
+      datasource.getPkgReleases.mockReset();
+      datasource.getPkgReleases.mockResolvedValueOnce({
+        releases: [],
+      });
+      await expect(getComposerConstraint({})).rejects.toThrow(
+        'No composer releases found.'
+      );
+    });
+
+    it('throws no compatible releases', async () => {
+      datasource.getPkgReleases.mockReset();
+      datasource.getPkgReleases.mockResolvedValueOnce({
+        releases: [{ version: '1.2.3' }],
+      });
+      await expect(
+        getComposerConstraint({ composer: '^3.1.0' })
+      ).rejects.toThrow('No compatible composer releases found.');
     });
   });
 
diff --git a/lib/manager/composer/utils.ts b/lib/manager/composer/utils.ts
index 8a77b5c75fc367512b38ec5becc5e7b59638ab72..7f84615fc91e2a83c03bd0dcd525603973cdfac8 100644
--- a/lib/manager/composer/utils.ts
+++ b/lib/manager/composer/utils.ts
@@ -1,17 +1,57 @@
+import { getPkgReleases } from '../../datasource';
 import { logger } from '../../logger';
 import { api, id as composerVersioningId } from '../../versioning/composer';
 import type { ComposerConfig, ComposerLock } from './types';
 
 export { composerVersioningId };
 
-export function getConstraint(constraints: Record<string, string>): string {
+export async function getComposerConstraint(
+  constraints: Record<string, string>
+): Promise<string> {
   const { composer } = constraints;
 
-  if (composer) {
-    logger.debug('Using composer constraint from config');
+  if (api.isSingleVersion(composer)) {
+    logger.debug(
+      { version: composer },
+      'Using composer constraint from config'
+    );
     return composer;
   }
 
+  const release = await getPkgReleases({
+    depName: 'composer/composer',
+    datasource: 'github-releases',
+    versioning: composerVersioningId,
+  });
+
+  if (!release?.releases?.length) {
+    throw new Error('No composer releases found.');
+  }
+  let versions = release.releases.map((r) => r.version);
+
+  if (composer) {
+    versions = versions.filter(
+      (v) => api.isValid(v) && api.matches(v, composer)
+    );
+  }
+
+  if (!versions.length) {
+    throw new Error('No compatible composer releases found.');
+  }
+
+  const version = versions.pop();
+  logger.debug({ range: composer, version }, 'Using composer constraint');
+  return version;
+}
+
+export function getPhpConstraint(constraints: Record<string, string>): string {
+  const { php } = constraints;
+
+  if (php) {
+    logger.debug('Using php constraint from config');
+    return php;
+  }
+
   return null;
 }