Commit composer.lock and Use composer install for Deterministic PHP Builds
Lock the dependency resolution with composer.lock and install exactly those versions. Use composer install, not update, in CI and production for deterministic PHP builds.
21 Jul 2026, 17:47 UTC

The build passes on your laptop and fails in CI because a transitive dependency moved. The useful takeaway is not to chase the latest versions on deploy; lock the resolution once and install exactly that.
What composer.lock actually records
composer.json declares version constraints, e.g. "^2.5". composer.lock records the exact resolved version for every package and its transitive dependencies, plus the dist URL and SHA256 hash used for the download.
Composer validates composer.lock against composer.json on install and fails if they drift. That forces an explicit decision before dependencies change, rather than a silent upgrade.
install vs update in practice
composer update resolves constraints anew and rewrites composer.lock. Running it in CI or production can pull a breaking change without review.
composer install reads composer.lock and installs those exact versions. It will not upgrade, and it will error if composer.json was changed without a corresponding lock update.
Where to run: in a clean checkout on the build agent or production server. Required permissions: read access to the project directory and write access to vendor/. Risk: running update in production rewrites the lock file and changes the dependency graph.
composer install --no-dev --optimize-autoloader--no-dev excludes development tooling from production while keeping production dependencies at their locked versions. --optimize-autoloader generates a class map for faster autoloading. --classmap-authoritative skips filesystem checks for mapped classes and should only be used when the autoloader map is complete and no classes are added at runtime.
Worked example for a reproducible deploy
A minimal composer.json constraint:
{
"require": {
"php": "^8.2",
"monolog/monolog": "^3.0"
}
}After a deliberate dependency review on a development machine:
composer update --lockCommit both composer.json and composer.lock to version control. In CI:
composer validate
composer install --dry-runcomposer validate confirms syntax. --dry-run verifies lock synchronization without modifying files. In the production-like stage:
composer install --no-dev --optimize-autoloaderCheck the result by inspecting vendor/autoload.php exists and that vendor/composer/installed.json reflects the locked versions. Inspect composer.lock for dist URLs and SHA256 hashes to confirm exact package sources are recorded.
Limitations to plan for
A lock file cannot protect against upstream package removal or changes to version control repositories after the lock was created. Composer checks platform requirements by default, so a lock file created on one PHP version may fail to install on another.
Version constraints in composer.json still govern composer update. Review them before committing lock file changes. Reproducibility can be checked by running composer install on two clean checkouts and comparing the resolved package versions in composer.lock.
Close the loop by treating composer.lock as a build artifact: update intentionally, review the diff, commit, and install deterministically everywhere else.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.