composer.json describes what is acceptable; composer.lock describes what was tested. Deploys that run update rather than install are resolving fresh on every release and shipping something no one has run.
# on a developer machine, deliberately, as a change
$ composer update vendor/package --with-dependencies
# everywhere else — CI, staging, production
$ composer install --no-dev --optimize-autoloader
# and the check that the two agree
$ composer validate --no-check-publish
./composer.json is valid
validate catches the case where the manifest was edited and the lock was not regenerated, which otherwise appears as a deploy that installs the previous version of something. Committing the lock file is not optional for an application; for a library it is the opposite, since the library’s consumers resolve their own. The distinction is worth stating in a README because it gets argued about roughly once a year.