For a PHP application, commit composer.lock and deploy with composer install, which uses the exact versions in the lock. For a library, the Composer documentation says committing the lock file is not necessary. The constraints in composer.json state what you accept; the lock records what you got.
How the main constraints behave
Composer's versions article explains the two operators most projects use.
| Constraint | Equivalent range | Notes |
|---|---|---|
| ~1.2 | 1.2 up to but not including 2.0.0 | Last digit you wrote can rise |
| ~1.2.3 | 1.2.3 up to but not including 1.3.0 | Patch updates only |
| ^1.2.3 | 1.2.3 up to but not including 2.0.0 | Sticks closer to semantic versioning |
| ^0.3 | 0.3.0 up to but not including 0.4.0 | Pre 1.0 handled conservatively |
| 1.0.* | 1.0 up to but not including 1.1 | Wildcard |
The documentation describes the caret as the recommended operator for maximum interoperability. It also warns that unbounded ranges can install versions that break backwards compatibility, and suggests the caret instead.
Install and update are different commands
Per the basic usage guide, the update command resolves everything in composer.json, writes exact versions to composer.lock, then installs. You should commit composer.lock so that everyone on the project is locked to the same versions.
When composer.lock already exists, running install resolves and installs what composer.json lists but uses the exact versions in the lock. The documentation says some dependencies may therefore not be at the latest available version, and that this is by design, so your project does not break because of unexpected changes.
composer install
Composer also states that it enables reproducible builds by default: running the same command repeatedly produces a vendor directory with identical files, except timestamps, including the autoloader files.
A routine for applications
- Keep composer.json constraints loose enough for fixes: caret for most packages.
- Commit composer.lock in the same change as any constraint edit.
- Deploy with composer install, never update, on servers and in CI.
- Run update in a dedicated branch, read the lock diff, test, merge.
- Add the vendor directory to .gitignore if your team does not commit it. The documentation suggests this for projects using git.
What about libraries
A library's consumers do not use your lock file. They resolve from your composer.json constraints. So state honest ranges, and treat the lock file, if you keep one, as a tool for your own tests. Use a caret on dependencies you have tested against and do not leave them unbounded.
Where drift hides
The lock fixes the version and the project holds its range, but a dependency's release tag is still a mutable pointer in version control. Composer's own documentation notes that Composer versions are resolved from version control refs such as tags and branches. Review lock diffs for unexpected sources or branch references, not just version numbers.
Where Safeguard fits
Safeguard's dependency scanning and SBOM generation can check what composer.lock resolves against known vulnerabilities, and you can try a free scan at gold.safeguard.sh/scan.
Try it yourself
Open the demo and follow how a vulnerable package is flagged and tracked, then think about which lock file diff would have fixed it.
Click through it at your own pace, or open it full screen.
Quick check
Should a PHP library commit composer.lock?
The Composer documentation says it is not necessary for libraries, because consumers resolve from composer.json.
Why did composer install not give me the newest version?
Because composer.lock exists. Install uses the exact versions in the lock by design. Run update when you want to move them.
Sources
Last reviewed: 19 October 2026.