Using uv workspaces
I have a pet project that has a build folder to build the database with a cli and an app folder with everything needed to run a Datasette based readonly website.
Both folders have a pyproject.toml and the app is deployed with a custom Dockerfile that uses uv.
To migrate them to use uv workspaces I added a toplevel pyproject.toml.
The uv docs state that every workspace needs a root which is itself a member, so the root gets both a [project] table and the [tool.uv.workspace] one:
[project] name = "k-workspace" version = "0.1.0" requires-python = ">=3.12" [tool.uv.workspace] members = ["app", "build"]
The sub folders have their own pyproject.toml -- unchanged.
For reference, app/pyproject.toml looks roughly like this (and build/pyproject.toml is analogous with name = "k-build"):
[project] name = "k-app" version = "0.1.0" requires-python = ">=3.12" dependencies = [ "datasette>=1.0a", "datasette-cluster-map", # ... ]
The name field here is what you pass to uv run --package later.
But we can remove the uv.lock files in both.
And create a new lock file for in the toplevel:
This produces one uv.lock at the root that resolves dependencies across both packages.
Running uv lock from a subdirectory still works — uv detects the workspace and uses the root lockfile.
To run a tool from a specific package, use --package:
uv run auto-syncs the target package's dependencies into the root .venv before executing, so you don't need to run uv sync first.
One thing to watch out for: plain uv sync at the root does not install the members' dependencies -- it syncs to whatever the root pyproject.toml declares (nothing, since the root package has no dependencies of its own).
It will also uninstall any packages in the .venv that don't match, so running uv sync after uv sync --all-packages strips the environment back down.
Not a disaster -- the next uv run --package … re-installs from uv's local cache in a second or two -- but surprising if you didn't expect it.
If you want both members installed at once (for example to poke around interactively), use:
Since each Dockerfile only copies its own subdirectory, there's no parent pyproject.toml visible inside the container. Uv treats the sub-package as a standalone project and resolves independently. No Dockerfile changes needed.
For only two sub projects this seems like not worth it, but I still like having only one .venv folder and uv.lock file.
See also the uv workspaces documentation.
