The composer.lock File
Every time you install or update packages, Composer writes (or updates) a second file alongside
composer.json: composer.lock. It looks like an implementation detail you can
ignore, but understanding it is essential to getting reproducible installs across machines.
What Is composer.lock?
composer.lock is a snapshot that records the exact version of every package that
was actually installed — not just the packages you listed directly in require, but every
transitive dependency those packages pulled in too. Where composer.json says "any
version matching this constraint is fine," composer.lock says "here is precisely which
version was chosen, down to the exact commit/release."
Why Constraints Alone Aren't Enough
Imagine your composer.json requires "some/package": "^1.2". That constraint is
satisfied by 1.2.0, by 1.4.7, and by 1.9.9 — any 1.x release from
1.2.0 onward. If you run composer install today, you might get 1.2.0.
If a teammate clones the repo and runs it next month, after new releases have shipped, they might get
1.5.3 instead — same composer.json, different actual code running. That gap is
exactly what composer.lock closes: once it exists, everyone installs the version it
records, not just "a version that matches."
Should You Commit composer.lock?
| Project Type | Commit composer.lock? |
|---|---|
| Applications (websites, APIs, internal tools) | Yes. Every teammate and every deployment installs identical versions, avoiding "it works on my machine" surprises. |
| Libraries (packages meant to be installed by others) | Typically no. Whoever installs your library already has their own project's lock file, which takes precedence for the final install — your library's lock file wouldn't even be consulted. |
That distinction trips a lot of people up at first, but it comes down to one question: is this
composer.lock the one actually controlling what gets installed? For an application, yes —
commit it. For a library, the consuming project's own lock file wins, so it's conventional to leave
yours out of version control (and add it to .gitignore).
composer install Reads the Lock File
When a composer.lock file is present, running composer install ignores the
broader constraints in composer.json and instead installs exactly what the lock
file specifies:
$ composer install
This is what makes onboarding a new teammate or deploying to a server reliable — they run one command
and get the identical dependency versions you tested with. This is different from
composer update, which re-resolves your constraints against the latest matching versions
and rewrites the lock file — the full comparison between the two commands is next.
composer install and composer update differ, and when to use each → composer update vs. composer install