uv
uv copied to clipboard
Cache invalidation on local dependencies when using docker
Hello.
I am having difficulties using uv in docker. My project structure has two python libraries, one is an API server, and the other is an internal library.
The problem I am encountering is that after building, when running the image, the uv run reinstalls my internal libraries, with the following message: Requirement installed, but not fresh: .... This is problematic, because as per docker best practices, it is good to ensure that the install actually happens on the build stage, and not in the actual running phase of my application.
When debugging the issue further, I found out that it happens because the ctime of my pyproject.toml of the internal library changes between the docker build and the actual run.
I finally validated that symptom using the following dockerfile:
FROM ubuntu:22.04
RUN touch a && stat a > /root/stat-a
RUN sleep 5
And running: docker build --no-cache . -t playground && docker run playground sh -c 'cat /root/stat-a && stat a'
The output that I received was:
File: a
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: e3h/227d Inode: 1336246 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2024-11-18 10:04:52.462498005 +0000
Modify: 2024-11-18 10:04:52.462498005 +0000
Change: 2024-11-18 10:04:52.462498005 +0000
Birth: 2024-11-18 10:04:52.462498005 +0000
---
File: a
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: e3h/227d Inode: 225316 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2024-11-18 10:04:52.000000000 +0000
Modify: 2024-11-18 10:04:52.000000000 +0000
Change: 2024-11-18 10:04:57.680498008 +0000
Birth: 2024-11-18 10:04:57.680498008 +0000
As seen, two things are changing between the build and run - both the inode number, and the Change & Birth times. This seems like an odd behavior of docker, but as I dived deeper it seems inherent to the layering system of docker.
I think that the solution to that issue should come from uv's side, as the usage for similar type of projects in docker should be very common.
A possible approach that would solve both the cache freshness problem and would work with docker's odd behavior would be using checksums for local dependencies freshness check.
Some additional information:
- uv version: v0.5.1
- Platform: MacOS with Docker Desktop
Thanks in advance! Yannay
Thanks for the write-up. I think it'd be much easier to help out if you could provide a full reproduction for the behavior you're seeing, just so I can understand how you're structuring and running your application.
Hey,
So I worked a bit to reproduce it, created this fork of the example from the official documentation of uv
Added one commit that changes the command to use uv run instead of modifying the ENV (which is better anyways in my opinion).
When running the build and then run:
$ docker build -t uv-example .
$ docker run uv-example
Uninstalled 1 package in 0.68ms
Installed 2 packages in 10ms
...
As seen in the last two rows, it uninstalled and reinstalled the package of the example, as the symptoms that I encountered.
Thanks for responding so fast!
Thanks! Interestingly, with that example I see:
❯ docker run uv-example
Installed 1 package in 6ms
Bytecode compiled 1143 files in 49ms
INFO Using path src/uv_docker_example
INFO Resolved absolute path /app/src/uv_docker_example
Am I misunderstanding the output, or does it look like a platform difference?
Yeah it does seem different, I am running this on MacOS with docker desktop.
Though in your output it still shows that it installed a package, is it the expected behavior? I would expect it to not install any package as we run uv sync in the dockerfile.
Sorry, I haven't looked at all -- I just noticed that it differed from the above so wanted to ask first. (I'm also on macOS with Docker Desktop.)
This is very odd then. I am running on M2 with macOS Sequoia 15.1 and Docker Desktop v4.35.1 with docker engine v27.3.1
Are ya'll definitely testing the same uv version? The image could be cached if you're just using that example repository.
So in my case at least, the installed package in Installed 1 package is Ruff, due to the use of --no-dev when syncing the Dockerfile. (uv run will sync dev by default.)
Have you considered using uv run --frozen in your CMD instead? I think that's more appropriate given that you may not even want to re-validate the lockfile on container startup.
(I'm also on Docker version 27.3.1. M3, Sonoma 14.5.)
Still the same when running with --frozen flag.
The uv version from inside the container is 0.5.3.
Could it be related to the VM options in docker?
I am using Apple Virtualization framework with Rosetta enabled and VirtioFS for file sharing implementation.
(I'm also on Docker version 27.3.1. M3, Sonoma 14.5.)
Hey @charliermarsh, is there any more information I can share that will help you reproduce it locally?
I also cannot reproduce (similarly, on an M3 with Docker Desktop)
❯ docker build -t uv-example .
[+] Building 3.0s (10/10) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 1.18kB 0.0s
=> [internal] load metadata for ghcr.io/astral-sh/uv:0.5.3-python3.12-bookworm-slim 0.8s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 106B 0.0s
=> [stage-0 1/5] FROM ghcr.io/astral-sh/uv:0.5.3-python3.12-bookworm-slim@sha256:090b8acad6ffaeb1fbbec6ed7b052c3837ae6c407b5164d0b09e61e9a38311ab 1.2s
=> => resolve ghcr.io/astral-sh/uv:0.5.3-python3.12-bookworm-slim@sha256:090b8acad6ffaeb1fbbec6ed7b052c3837ae6c407b5164d0b09e61e9a38311ab 0.0s
=> => sha256:6c1729b16fda6af1e58192a3723fb0a8229a126ea9ee1e73ff5a2aae275ca1fb 1.25kB / 1.25kB 0.0s
=> => sha256:b0751293b2c29a56170f36ed84d79f872bb2923e426099166290e46865865459 6.35kB / 6.35kB 0.0s
=> => sha256:2d87e44c35ccf73088373150fe7de8d6b2b2958dd33d8d0c2825048a687a4f7e 13.52MB / 13.52MB 1.1s
=> => sha256:090b8acad6ffaeb1fbbec6ed7b052c3837ae6c407b5164d0b09e61e9a38311ab 2.22kB / 2.22kB 0.0s
=> => extracting sha256:2d87e44c35ccf73088373150fe7de8d6b2b2958dd33d8d0c2825048a687a4f7e 0.1s
=> [internal] load build context 0.0s
=> => transferring context: 205.50kB 0.0s
=> [stage-0 2/5] WORKDIR /app 0.0s
=> [stage-0 3/5] RUN --mount=type=cache,target=/root/.cache/uv --mount=type=bind,source=uv.lock,target=uv.lock --mount=type=bind,source=pyproject.toml,target=pyproject.toml uv sync --frozen --no-install-project --no-dev 0.4s
=> [stage-0 4/5] ADD . /app 0.0s
=> [stage-0 5/5] RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev 0.4s
=> exporting to image 0.1s
=> => exporting layers 0.1s
=> => writing image sha256:be88ded41d630245cb1ef290561c9885f61b039ef7381cced4de0abf15de5bdf 0.0s
=> => naming to docker.io/library/uv-example 0.0s
What's next:
View a summary of image vulnerabilities and recommendations → docker scout quickview
❯ docker run uv-example
Installed 1 package in 14ms
diff --git a/Dockerfile b/Dockerfile
index d7acf90..f0aed3f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# Use a Python image with uv pre-installed
-FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
+FROM ghcr.io/astral-sh/uv:0.5.3-python3.12-bookworm-slim
# Install the project into `/app`
WORKDIR /app
I'm also using the default settings, which afaict includes VirtioFS, Rosetta, and the Virtualization framework.
Same issue on my side - following this example leads to Uninstalled 1 package [..] Installed 1 package. Using --verbose on uv run surfaces "Requirement installed, but not fresh", with the requirement being the project itself.
My colleagues using Docker Desktop on Windows are encountering this as well. Is there a way to bypass or "trick" the freshness check? How does uv decide whether a package is not fresh and requires rebuilding?
Me and other my team mates are encountering this issue. Everyone with the issue is running Docker Desktop, either on macOS or Windows. Team mates who have installed Docker community edition in WSL2 do not have this issue. We noticed the issue rather quickly after we switched to using uv because we:
- run docker build where Dockerfile runs uv to install dependencies using
uv sync --frozen - using the image from step 1, we
docker run --rm --network none uv run --frozen pytest(and pylint and other tools)
The step 2 fails for people using Docker Desktop because uv tries to rebuild the project and download some libraries (setuptools). Step 2 succeeds for people using Docker community edition.
Of course we can work around the problem by removing --network none but it revealed this problem that uv tries to rebuild the python project during docker run.
Is there any workaround? I don't want to re-install anything on container startup.
Use this command
.venv/bin/python main.py
instead of
uv run main.py
We are switching to Chainguard images, which requires a multi-stage build where you build in one stage with the "-dev" container, and copy the build artifacts into the final stage which doesn't have /bin/sh, etc.
The Docker COPY command doesn't preserve timestamps, so in the final stage when it tries to run the program:
CMD [ "uv", "run", ... ]
uv says the "Requirement installed, but not fresh" for the project itself and it tries to reinstall.
We can't just call the project directly vs uv run because the library we use (Prefect) calls uv run internally with lots of arguments l don't have control over like --python and --with
I would like an environment variable that tells uv not to try and do anything and just run the program.
edit it seems this combination of environment variables at the end of the Dockerfile works:
ENV UV_NO_MANAGED_PYTHON=true \
UV_SYSTEM_PYTHON=true \
UV_LOCKED=1 \
UV_NO_SYNC=1 \
UV_NO_CACHE=1 \
UV_NO_DEV=1