Why this matters
composer.lock records the exact versions of every dependency that one particular project installed. For an application, committing it is right: everyone who deploys the application gets the same versions. For a library, it is a different story. When someone installs your library, Composer ignores your lockfile completely and resolves versions against their own project. Shipping it changes nothing about how your library installs.
What it does change is what security scanners see. Tools that walk a project's vendor/ directory find your composer.lock, read the versions pinned inside it, and report vulnerabilities in packages the consuming project may not even have installed at those versions. Every downstream user gets false alarms that trace back to your package.
What good looks like
The archive that Composer downloads for your library does not contain composer.lock. Whether you commit the file to the repository is a separate choice; what matters is that it stays out of the released archive.
How to do it
You have two options, and you can use both.
Option 1: keep the lockfile out of the archive
Keep composer.lock in the repository for reproducible CI, but exclude it from the release archive with .gitattributes:
/composer.lock export-ignore
Git honours export-ignore when building archives, and Packagist and GitHub build release archives with Git, so the file disappears from what users download. Commit the .gitattributes change and tag a new release.
Alternatively, list it under archive.exclude in composer.json:
{
"archive": {
"exclude": ["/composer.lock"]
}
}
Option 2: do not commit it at all
Add composer.lock to .gitignore and remove it from the repository:
echo "/composer.lock" >> .gitignore
git rm --cached composer.lock
git commit -m "Stop committing composer.lock"
Your CI then resolves fresh dependencies on every run, which has a side benefit: you find out quickly when a new dependency release breaks your library.
Tag a release
The released archive is what counts. After either change, tag a new version so the fix reaches users.
Things to watch for
- Fixing the repository but not releasing. The old archive still contains the lockfile until a new tag is published.
- Applications and skeletons. If your
composer.jsondeclares"type": "project", committing and shipping a lockfile is correct. This guidance is for libraries, plugins, and bundles. - Monorepos. A lockfile at the monorepo root is fine; what matters is the archive of each published package.
- Losing reproducible CI. If you stop committing the lockfile, consider running CI with both
--prefer-lowestand the latest versions so you test the whole range you declare.