uv pip sync makes the environment match your requirements file exactly. uv pip install does not remove packages that are already installed unless they conflict, so leftovers can remain. After moving from pip to uv, use sync in CI and images, and use project commands such as uv sync, which also removes extras by default.
The difference in one table
| Command | Removes packages not in the file? |
|---|---|
| uv pip install | No, unless they conflict with the lockfile |
| uv pip sync | Yes, the environment matches the file |
| uv sync (project mode) | Yes by default, exact syncing |
| uv run (project mode) | No by default, inexact syncing |
This comes straight from the uv documentation. The pip interface page says packages already installed will not be removed unless they conflict, so the environment can contain dependencies that are not declared in the lockfile, which is not great for reproducibility, and recommends sync to make the environment exactly match. The project documentation says uv sync performs exact syncing and removes packages not in the lockfile, while uv run is inexact by default, with flags to change both.
Why leftovers are a security problem
Extra packages are code that runs with your application, but that nothing reviewed. A developer once installed a debugging tool, a build once pulled a test helper, an old pinned version lingered. A scanner that reads the lockfile will not see any of them, while the running process can import them.
In a migration the risk is higher, because people carry old virtual environments across tools. The old environment built by pip and the new lockfile from uv may not match, and a plain install on top hides the difference.
Steps after migrating
- In CI and Dockerfiles, start from a clean environment, or use the sync form that makes it exact.
- Compile or export the lock, then sync from it. The documentation shows compiling a requirements file and then syncing an environment with that file, and a pylock.toml is also supported.
- In project mode, use the locked flag so a stale lockfile fails instead of changing. See uv locked or frozen: which flag for CI.
- For hash checking on runtime packages, use the hash requirement option with install or sync, as the docs state. See how pip hash checking mode works.
- Recreate developer environments after the migration instead of reusing old ones.
uv pip compile requirements.in -o requirements.txt
uv pip sync requirements.txt
Check what is installed
List the installed packages and compare them with the lockfile, using the method in verify resolved versions after switching package managers. Any package present in one but not the other deserves an explanation.
Also useful: does pip freeze pin transitive dependencies.
Try it yourself
Click through it at your own pace, or open it full screen. Compare the inventory of declared components with what is actually installed in a running environment.
Safeguard's dependency scanning and SBOM generation inventory what a repository declares. A free scan is at gold.safeguard.sh/scan.
Quick check
Does uv sync remove extra packages?
By default yes, it performs exact syncing, per the uv documentation. An option keeps extras when you need it.
Why not just run uv pip install in a Dockerfile?
On a fresh image it behaves the same, but on a reused environment it keeps leftovers. Sync is the safer default.
Sources
Last reviewed: 19 October 2026.