Use a uv workspace when your Python packages can live with one shared lockfile and one set of resolved versions. Use independent projects with path dependencies when members have conflicting requirements or need their own virtual environments. The first gives consistency, the second gives isolation.
How a uv workspace is set up
Add a tool.uv.workspace table to a pyproject.toml. The members list is required and exclude is optional, both taking globs. Every matched directory needs its own pyproject.toml, and the root is also a member.
[project]
name = "albatross"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["bird-feeder", "tqdm>=4,<5"]
[tool.uv.sources]
bird-feeder = { workspace = true }
[tool.uv.workspace]
members = ["packages/*"]
exclude = ["packages/seeds"]
The sources entry with workspace = true makes bird-feeder resolve to the local member instead of PyPI. Dependencies between members are installed as editable.
What you get
- One uv.lock for the whole workspace. Running uv lock resolves everything together.
- uv run and uv sync target the root by default, and the package flag targets a member.
- Sources defined in the root apply to all members unless a member defines its own for the same dependency.
One lockfile means one place to review and one answer to "which version of this library do we ship."
What it costs
According to the uv documentation, a workspace enforces a single requires-python, the intersection of all members' values. Also, Python has no dependency isolation, so uv cannot stop one member from importing a package that only another member declares. That second point is easy to miss: a test can pass because a sibling happened to install the package.
When to choose path dependencies
The uv docs say not to use a workspace when members have conflicting requirements, or when each member needs its own virtual environment. In that case make each package an independent project and reference siblings by path:
[tool.uv.sources]
bird-feeder = { path = "packages/bird-feeder" }
You get finer control over resolution and environments. The documented trade is that the package flag of uv run is not available.
Decision table
| Situation | Choice |
|---|---|
| Libraries released together, compatible pins | Workspace |
| Services with different Python or library needs | Path dependencies |
| You want one lockfile to review | Workspace |
| You need separate environments per member | Path dependencies |
Security notes
Name collisions matter in both models. A workspace member called by a name that also exists on PyPI is safe only while the workspace source is set. Keep the sources entry for every internal package, and read pip versus uv index handling for the index side. Commit the lockfile, and consider hash checking as in pip hash checking for AI added dependencies.
Safeguard dependency and SBOM scanning can list the Python packages in a repository and flag known vulnerabilities. You can try a free scan at gold.safeguard.sh/scan.
Quick check
Does a uv workspace create one environment per member?
No. It shares one lockfile. If members need separate environments, use independent projects.
How do I make a dependency resolve to a sibling package?
Add it to tool.uv.sources with workspace = true.
Sources
Last reviewed: 19 October 2026.