Commit composer.lock and use composer install for reproducible PHP deploys
Commit composer.lock and run composer install—not composer update—to keep PHP dependency installs deterministic across CI and production.
15 Jul 2025, 21:11 UTC

The problem: a silent upgrade in the deployment pipeline
A PHP application works on your laptop and in staging. On Friday, the deployment pipeline runs composer update to fetch fresh dependencies. A package releases a compatible minor version that changes a behavior your code relies on. Production breaks, but your local environment still uses the older version you installed weeks ago.
The useful takeaway is simple: commit composer.lock to version control and run composer install in CI and deployment, not composer update. This makes dependency installs deterministic across environments.
What composer.lock actually locks
composer.json declares version constraints, such as ^1.2. composer.lock records the exact resolved versions, content hashes, and the full dependency graph that Composer solved at lock time. When you run composer install, Composer reads the lock file and installs those exact versions. When you run composer update, Composer may discard the old resolution and write a new lock file, which is how surprise upgrades enter a pipeline.
This guidance assumes Composer 2.x. Check your toolchain with composer --version; older Composer 1 releases may not understand newer lock metadata.
CI and deployment: install, don't update
| Command | Respects composer.lock | Modifies composer.lock | Typical use |
|---|---|---|---|
composer install | Yes | No | CI, deployment, application checkouts |
composer update | No | Yes | Intentional dependency upgrades in a feature branch |
Run validation and installation in the project root on the CI runner or deployment host. The user needs read access to the repository and write access to vendor/.
composer validate
composer install --no-dev --optimize-autoloader
--no-dev excludes development-only dependencies such as testing frameworks. --optimize-autoloader builds a class map so production does not scan the filesystem for every class load.
Expected result: a zero exit code and a populated vendor/ directory. If composer.json and composer.lock are out of sync, composer install refuses to run. Do not edit composer.lock by hand; hash mismatches can cause failures or inconsistent installs. Reconcile the files intentionally with composer update or composer require in a branch, test the change, and commit both files together.
Worked example: verify a clean install
- Check out the repository into a clean directory.
- Run
composer validateto confirmcomposer.jsonandcomposer.lockare well-formed before installing. - Run
composer install --no-dev --optimize-autoloader. - Open
vendor/composer/installed.jsonand confirm the package versions matchcomposer.lock. - Repeat on a second environment. Identical lock content should produce the same package set.
If you need installs to match a production PHP version that differs from your local machine, pin platform requirements in composer.json:
{
"config": {
"platform": {
"php": "8.2",
"ext-json": "1.0"
}
}
}
This tells Composer to resolve packages as if running PHP 8.2 with JSON extension 1.0, even when the local command runs elsewhere. It does not install PHP or extensions; those must be provisioned on the server separately.
Limitations
- The lock file pins PHP package versions, not system-level dependencies such as PHP extensions or OS libraries. Provision those through your container image or server configuration.
- Applications should generally commit
composer.lock. Reusable libraries often omit it so consumers can resolve their own version ranges. - A lock file does not protect against vulnerable package versions; it only makes the installed set predictable.
Closing checklist
- Commit
composer.lockfor applications. - Run
composer install, nevercomposer update, in CI and deployment jobs. - Run
composer validatebefore committing dependency changes. - Verify a clean checkout reproduces the same package set in a second environment.
- Pin target PHP and extensions with
config.platformwhen local and production environments differ.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.