diff --git a/lib/manager/composer/__snapshots__/extract.spec.ts.snap b/lib/manager/composer/__snapshots__/extract.spec.ts.snap
index 22d84372354b698dffa317fd1e86701597842fca..8e7f636a648c7c9e4e4fdbd0cac6d3795a7fe2e0 100644
--- a/lib/manager/composer/__snapshots__/extract.spec.ts.snap
+++ b/lib/manager/composer/__snapshots__/extract.spec.ts.snap
@@ -2,10 +2,6 @@
 
 exports[`manager/composer/extract extractPackageFile() extracts dependencies with lock file 1`] = `
 Object {
-  "constraints": Object {
-    "composer": "^1.10.0",
-    "php": ">=5.3.2",
-  },
   "deps": Array [
     Object {
       "currentValue": ">=5.3.2",
@@ -216,10 +212,6 @@ Object {
 
 exports[`manager/composer/extract extractPackageFile() extracts dependencies with no lock file 1`] = `
 Object {
-  "constraints": Object {
-    "composer": "^1.10.0",
-    "php": ">=5.3.2",
-  },
   "deps": Array [
     Object {
       "currentValue": ">=5.3.2",
@@ -427,10 +419,6 @@ Object {
 
 exports[`manager/composer/extract extractPackageFile() extracts object registryUrls 1`] = `
 Object {
-  "constraints": Object {
-    "composer": "1.*",
-    "php": ">=5.5",
-  },
   "deps": Array [
     Object {
       "currentValue": ">=5.5",
@@ -532,9 +520,6 @@ Object {
 
 exports[`manager/composer/extract extractPackageFile() extracts object repositories and registryUrls with lock file 1`] = `
 Object {
-  "constraints": Object {
-    "composer": "1.*",
-  },
   "deps": Array [
     Object {
       "currentValue": "*",
@@ -572,9 +557,6 @@ Object {
 
 exports[`manager/composer/extract extractPackageFile() extracts registryUrls 1`] = `
 Object {
-  "constraints": Object {
-    "composer": "^1.10.0",
-  },
   "deps": Array [
     Object {
       "currentValue": "*",
@@ -617,9 +599,6 @@ Object {
 
 exports[`manager/composer/extract extractPackageFile() extracts repositories and registryUrls 1`] = `
 Object {
-  "constraints": Object {
-    "composer": "1.*",
-  },
   "deps": Array [
     Object {
       "currentValue": "*",
diff --git a/lib/manager/composer/artifacts.spec.ts b/lib/manager/composer/artifacts.spec.ts
index f160b108c915950bbc46f611a700d3675cf8b7dc..012e7c345854e8b35e2b90e8d51015a4c7a6074a 100644
--- a/lib/manager/composer/artifacts.spec.ts
+++ b/lib/manager/composer/artifacts.spec.ts
@@ -1,6 +1,5 @@
-import { exec as _exec } from 'child_process';
 import { join } from 'upath';
-import { envMock, mockExecAll } from '../../../test/exec-util';
+import { envMock, exec, mockExecAll } from '../../../test/exec-util';
 import { env, fs, git, mocked, partial } from '../../../test/util';
 import { setAdminConfig } from '../../config/admin';
 import type { RepoAdminConfig } from '../../config/types';
@@ -22,7 +21,6 @@ jest.mock('../../../lib/datasource');
 jest.mock('../../util/fs');
 jest.mock('../../util/git');
 
-const exec: jest.Mock<typeof _exec> = _exec as any;
 const datasource = mocked(_datasource);
 
 const config: UpdateArtifactsConfig = {
@@ -55,9 +53,11 @@ describe('.updateArtifacts()', () => {
       join(adminConfig.cacheDir, './others/composer')
     );
   });
+
   afterEach(() => {
     setAdminConfig();
   });
+
   it('returns if no composer.lock found', async () => {
     expect(
       await composer.updateArtifacts({
@@ -68,10 +68,11 @@ describe('.updateArtifacts()', () => {
       })
     ).toBeNull();
   });
+
   it('returns null if unchanged', async () => {
-    fs.readLocalFile.mockResolvedValueOnce('Current composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{}');
     const execSnapshots = mockExecAll(exec);
-    fs.readLocalFile.mockReturnValueOnce('Current composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{}');
     git.getRepoStatus.mockResolvedValue(repoStatus);
     setAdminConfig({ ...adminConfig, allowScripts: true });
     expect(
@@ -84,6 +85,7 @@ describe('.updateArtifacts()', () => {
     ).toBeNull();
     expect(execSnapshots).toMatchSnapshot();
   });
+
   it('uses hostRules to set COMPOSER_AUTH', async () => {
     hostRules.add({
       hostType: PLATFORM_TYPE_GITHUB,
@@ -112,9 +114,9 @@ describe('.updateArtifacts()', () => {
       username: 'some-other-username',
       password: 'some-other-password',
     });
-    fs.readLocalFile.mockResolvedValueOnce('Current composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{}');
     const execSnapshots = mockExecAll(exec);
-    fs.readLocalFile.mockReturnValueOnce('Current composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{}');
     const authConfig = {
       ...config,
       registryUrls: ['https://packagist.renovatebot.com'],
@@ -130,10 +132,11 @@ describe('.updateArtifacts()', () => {
     ).toBeNull();
     expect(execSnapshots).toMatchSnapshot();
   });
+
   it('returns updated composer.lock', async () => {
-    fs.readLocalFile.mockResolvedValueOnce('Current composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{}');
     const execSnapshots = mockExecAll(exec);
-    fs.readLocalFile.mockReturnValueOnce('New composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{}');
     git.getRepoStatus.mockResolvedValue({
       ...repoStatus,
       modified: ['composer.lock'],
@@ -148,12 +151,13 @@ describe('.updateArtifacts()', () => {
     ).not.toBeNull();
     expect(execSnapshots).toMatchSnapshot();
   });
+
   it('supports vendor directory update', async () => {
     const foo = join('vendor/foo/Foo.php');
     const bar = join('vendor/bar/Bar.php');
     const baz = join('vendor/baz/Baz.php');
     fs.localPathExists.mockResolvedValueOnce(true);
-    fs.readLocalFile.mockResolvedValueOnce('Current composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{}');
     const execSnapshots = mockExecAll(exec);
     git.getRepoStatus.mockResolvedValueOnce({
       ...repoStatus,
@@ -161,10 +165,10 @@ describe('.updateArtifacts()', () => {
       not_added: [bar],
       deleted: [baz],
     });
-    fs.readLocalFile.mockResolvedValueOnce('New composer.lock' as any);
-    fs.readLocalFile.mockResolvedValueOnce('Foo' as any);
-    fs.readLocalFile.mockResolvedValueOnce('Bar' as any);
-    fs.getSiblingFileName.mockReturnValueOnce('vendor' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{  }');
+    fs.readLocalFile.mockResolvedValueOnce('Foo');
+    fs.readLocalFile.mockResolvedValueOnce('Bar');
+    fs.getSiblingFileName.mockReturnValueOnce('vendor');
     const res = await composer.updateArtifacts({
       packageFileName: 'composer.json',
       updatedDeps: [],
@@ -173,17 +177,18 @@ describe('.updateArtifacts()', () => {
     });
     expect(res).not.toBeNull();
     expect(res?.map(({ file }) => file)).toEqual([
-      { contents: 'New composer.lock', name: 'composer.lock' },
+      { contents: '{  }', name: 'composer.lock' },
       { contents: 'Foo', name: foo },
       { contents: 'Bar', name: bar },
       { contents: baz, name: '|delete|' },
     ]);
     expect(execSnapshots).toMatchSnapshot();
   });
+
   it('performs lockFileMaintenance', async () => {
-    fs.readLocalFile.mockResolvedValueOnce('Current composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{}');
     const execSnapshots = mockExecAll(exec);
-    fs.readLocalFile.mockReturnValueOnce('New composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{  }');
     git.getRepoStatus.mockResolvedValue({
       ...repoStatus,
       modified: ['composer.lock'],
@@ -201,13 +206,14 @@ describe('.updateArtifacts()', () => {
     ).not.toBeNull();
     expect(execSnapshots).toMatchSnapshot();
   });
+
   it('supports docker mode', async () => {
     setAdminConfig({ ...adminConfig, binarySource: 'docker' });
-    fs.readLocalFile.mockResolvedValueOnce('Current composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{}');
 
     const execSnapshots = mockExecAll(exec);
 
-    fs.readLocalFile.mockReturnValueOnce('New composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{  }');
     datasource.getPkgReleases.mockResolvedValueOnce({
       releases: [
         { version: '1.10.0' },
@@ -232,11 +238,12 @@ describe('.updateArtifacts()', () => {
     ).not.toBeNull();
     expect(execSnapshots).toMatchSnapshot();
   });
+
   it('supports global mode', async () => {
     setAdminConfig({ ...adminConfig, binarySource: 'global' });
-    fs.readLocalFile.mockResolvedValueOnce('Current composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{}');
     const execSnapshots = mockExecAll(exec);
-    fs.readLocalFile.mockReturnValueOnce('New composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{ }');
     git.getRepoStatus.mockResolvedValue({
       ...repoStatus,
       modified: ['composer.lock'],
@@ -251,8 +258,9 @@ describe('.updateArtifacts()', () => {
     ).not.toBeNull();
     expect(execSnapshots).toMatchSnapshot();
   });
+
   it('catches errors', async () => {
-    fs.readLocalFile.mockResolvedValueOnce('Current composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{}');
     fs.writeLocalFile.mockImplementationOnce(() => {
       throw new Error('not found');
     });
@@ -265,8 +273,9 @@ describe('.updateArtifacts()', () => {
       })
     ).toMatchSnapshot();
   });
+
   it('catches unmet requirements errors', async () => {
-    fs.readLocalFile.mockResolvedValueOnce('Current composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{}');
     fs.writeLocalFile.mockImplementationOnce(() => {
       throw new Error(
         'fooYour requirements could not be resolved to an installable set of packages.bar'
@@ -281,8 +290,9 @@ describe('.updateArtifacts()', () => {
       })
     ).toMatchSnapshot();
   });
+
   it('throws for disk space', async () => {
-    fs.readLocalFile.mockResolvedValueOnce('Current composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{}');
     fs.writeLocalFile.mockImplementationOnce(() => {
       throw new Error(
         'vendor/composer/07fe2366/sebastianbergmann-php-code-coverage-c896779/src/Report/Html/Renderer/Template/js/d3.min.js:  write error (disk full?).  Continue? (y/n/^C) '
@@ -297,10 +307,11 @@ describe('.updateArtifacts()', () => {
       })
     ).rejects.toThrow();
   });
+
   it('disables ignorePlatformReqs', async () => {
-    fs.readLocalFile.mockResolvedValueOnce('Current composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{}');
     const execSnapshots = mockExecAll(exec);
-    fs.readLocalFile.mockReturnValueOnce('New composer.lock' as any);
+    fs.readLocalFile.mockResolvedValueOnce('{ }');
     git.getRepoStatus.mockResolvedValue({
       ...repoStatus,
       modified: ['composer.lock'],
diff --git a/lib/manager/composer/artifacts.ts b/lib/manager/composer/artifacts.ts
index 9d6af8df8c6abbc3fb6ce337d93209bc5ecec927..9beef4f7de72780e71bbc6472e684a963e9c596c 100644
--- a/lib/manager/composer/artifacts.ts
+++ b/lib/manager/composer/artifacts.ts
@@ -25,7 +25,11 @@ import { getRepoStatus } from '../../util/git';
 import * as hostRules from '../../util/host-rules';
 import type { UpdateArtifact, UpdateArtifactsResult } from '../types';
 import type { AuthJson } from './types';
-import { composerVersioningId, getConstraint } from './utils';
+import {
+  composerVersioningId,
+  extractContraints,
+  getConstraint,
+} from './utils';
 
 function getAuthJson(): string | null {
   const authJson: AuthJson = {};
@@ -82,7 +86,7 @@ export async function updateArtifacts({
   );
 
   const lockFileName = packageFileName.replace(/\.json$/, '.lock');
-  const existingLockFileContent = await readLocalFile(lockFileName);
+  const existingLockFileContent = await readLocalFile(lockFileName, 'utf8');
   if (!existingLockFileContent) {
     logger.debug('No composer.lock found');
     return null;
@@ -93,6 +97,15 @@ export async function updateArtifacts({
   await ensureLocalDir(vendorDir);
   try {
     await writeLocalFile(packageFileName, newPackageFileContent);
+
+    const constraints = {
+      ...extractContraints(
+        JSON.parse(newPackageFileContent),
+        JSON.parse(existingLockFileContent)
+      ),
+      ...config.constraints,
+    };
+
     if (config.isLockFileMaintenance) {
       await deleteLocalFile(lockFileName);
     }
@@ -105,7 +118,7 @@ export async function updateArtifacts({
       },
       docker: {
         image: 'composer',
-        tagConstraint: getConstraint(config),
+        tagConstraint: getConstraint(constraints),
         tagScheme: composerVersioningId,
       },
     };
diff --git a/lib/manager/composer/extract.ts b/lib/manager/composer/extract.ts
index e0faa9fed445a9491cb708f91c9169499b379cfa..e651bd9d81662071eaff0bdfa868b6d7fa2e9664 100644
--- a/lib/manager/composer/extract.ts
+++ b/lib/manager/composer/extract.ts
@@ -12,7 +12,6 @@ import type {
   ComposerManagerData,
   Repo,
 } from './types';
-import { extractContraints } from './utils';
 
 /**
  * The regUrl is expected to be a base URL. GitLab composer repository installation guide specifies
@@ -116,8 +115,6 @@ export async function extractPackageFile(
     res.registryUrls = registryUrls;
   }
 
-  res.constraints = extractContraints(composerJson, lockParsed);
-
   const deps = [];
   const depTypes = ['require', 'require-dev'];
   for (const depType of depTypes) {
diff --git a/lib/manager/composer/utils.spec.ts b/lib/manager/composer/utils.spec.ts
index 824751e824f607f29acb3d22f3361c45c65ff288..6c488a0d0c85cb92cb55b070e42838a42aa4f7e0 100644
--- a/lib/manager/composer/utils.spec.ts
+++ b/lib/manager/composer/utils.spec.ts
@@ -4,9 +4,7 @@ import { extractContraints, getConstraint } from './utils';
 describe(getName(), () => {
   describe('getConstraint', () => {
     it('returns from config', () => {
-      expect(getConstraint({ constraints: { composer: '1.1.0' } })).toEqual(
-        '1.1.0'
-      );
+      expect(getConstraint({ composer: '1.1.0' })).toEqual('1.1.0');
     });
 
     it('returns from null', () => {
diff --git a/lib/manager/composer/utils.ts b/lib/manager/composer/utils.ts
index d5f516e1e8fb7b88d5f97392ff1dc5a97660ec81..8a77b5c75fc367512b38ec5becc5e7b59638ab72 100644
--- a/lib/manager/composer/utils.ts
+++ b/lib/manager/composer/utils.ts
@@ -1,12 +1,10 @@
 import { logger } from '../../logger';
 import { api, id as composerVersioningId } from '../../versioning/composer';
-import type { UpdateArtifactsConfig } from '../types';
 import type { ComposerConfig, ComposerLock } from './types';
 
 export { composerVersioningId };
 
-export function getConstraint(config: UpdateArtifactsConfig): string {
-  const { constraints = {} } = config;
+export function getConstraint(constraints: Record<string, string>): string {
   const { composer } = constraints;
 
   if (composer) {