Choosing Between Composer's --prefer-dist and --prefer-source: A Decision Guide
Decide between Composer's --prefer-dist and --prefer-source with a constraint checklist, a comparison table, and concrete ways to verify which mode actually installed your vendor packages.
30 Nov 2025, 17:24 UTC

Your CI build takes eight minutes, and three of them are Composer cloning full Git histories for packages you will never edit. Or the opposite problem: you need to patch a vendor library to debug a production bug, but composer install gave you a plain zip with no Git metadata, so your changes can't be committed or diffed cleanly. Both problems come from the same decision: whether dependencies install from packaged distributions or from version-control source.
The useful takeaway: use --prefer-dist (Composer's default behavior when available) for CI and production builds, and --prefer-source only on machines where someone will actually edit or debug vendor code. You can encode this per project in composer.json or per command on the CLI.
The decision and its constraints
Composer can obtain a package two ways. A dist is a packaged zip or tarball, usually served from GitHub/GitLab release archives or mirrored by Packagist. A source install clones the package's VCS repository (Git, and historically hg/svn) into vendor/.
Constraints that decide this for you:
- Dist requires network access to the dist URLs (typically the hosting platform's archive endpoints). No VCS tooling needed.
- Source requires
git(or the relevant VCS binary) installed on the machine and network access to the repositories. In a minimal Docker image without Git, source installs fail or fall back. - The lock file records which reference was resolved. Mixing modes across environments is fine for dist/source of the same commit, but switching modes after a lock exists can require a fresh
composer updateor deletingcomposer.lockto change what is recorded.
Comparing the two options
| Aspect | --prefer-dist | --prefer-source |
|---|---|---|
| What is fetched | Zip/tar archive of one version | Full VCS clone |
| Install speed | Faster (single archive download) | Slower (clone + history) |
| Disk usage in vendor/ | Smaller, no .git directory | Larger, includes .git |
| Can edit/commit vendor code | No (plain files) | Yes (working repo) |
| Tooling required | None beyond PHP/Composer | git (or hg/svn) installed |
| Typical environment | CI, production, containers | Local development, contributing upstream |
Trade-offs in practice
Dist wins on build time and artifact size. In a container image, avoiding VCS clones means smaller layers and no need to install Git in the final image — a real security and size benefit since Git in a production image is dead weight and extra attack surface.
Source wins when you need to modify a dependency: debugging into a library with breakpoints that survive reinstalls, testing a patch before submitting it upstream, or maintaining a fork workflow. With a dist install, edits in vendor/ are untracked and silently overwritten by the next composer install.
A common failure mode: setting prefer-source globally in a base Docker image or CI config, then watching builds break on a machine without Git or with no access to private repository hosts. The reverse failure — a developer trying to patch a dist-installed package — is merely annoying, not build-breaking.
Concrete implementation
For a production-oriented project, pin the preference in composer.json so every environment behaves the same:
{
"config": {
"prefer-dist": true,
"prefer-source": false
}
}
For a one-off local install where you intend to hack on a dependency, override on the CLI (run in the project root; no special permissions needed):
composer install --prefer-source
You can also scope source installs to a single package, which is the pragmatic middle ground — only the package you plan to edit gets cloned:
composer require vendor/package --prefer-source
Risk to note: --prefer-source on a machine without Git will either fail or silently fall back to dist depending on the situation, so don't rely on it in automated builds unless you've verified Git is present.
Validating which mode you actually got
Don't trust the flag — verify the result. Three quick checks, all run in the project root:
- Filesystem check: after a source install,
vendor/<vendor>/<package>/.gitexists. After a dist install, it does not. Example:ls vendor/monolog/monolog/.git— a "No such file or directory" result confirms dist. - Lock file check: open
composer.lockand find the package. Dist installs record a"dist"block with a URL and reference; source installs include a"source"block with"type": "git". Both may appear, but the presence of a.gitdirectory is the ground truth for what is on disk. - Re-install check: run
composer installagain and watch output — source installs show "Cloning" messages, dist installs show "Downloading" of archives.
Limitations
These flags are preferences, not guarantees: if a package publishes no dist archive, Composer falls back to source regardless of --prefer-dist. Behavior described here reflects Composer 2.x defaults; Composer 1.x differed in caching and fallback details, so verify against your installed version with composer --version. Also note that composer.lock pins exact references — changing preference flags does not change already-locked versions, only how their files land on disk.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.