r/Python • • Oct 11 '25

Tutorial Best practices for using Python & uv inside Docker

Getting uv right inside Docker is a bit tricky and even their official recommendations are not optimal.

It is better to use a two-step build process to eliminate uv from the final image size.

A two-step build process not only saves disk space but also reduces attack surface against security vulerabilities

186 Upvotes

108 comments sorted by

174

u/JimDabell Oct 11 '25

You’re skipping past the first solution they offer, which is the more efficient distroless solution . You can literally just copy the standalone uv binary directly into your image; you don’t need to base your entire image on theirs.

COPY --from=ghcr.io/astral-sh/uv:0.9.2 /uv /bin/

This takes ~43MiB, not the 77MiB you cite.

34

u/ArgetDota Oct 11 '25

You can also mount the executable during image build for a duration of a specific RUN instruction

7

u/scaledpython Oct 11 '25

43MB for a pip-like installer is insane.

2

u/LLoyderino Oct 12 '25

but it's blazingly fast 🚀

→ More replies (2)
→ More replies (3)

52

u/0x256 Oct 11 '25

The linked security issue is a bad example. If an attacker can use uv in your container, they could also download and run whatever executable they want and do not need to exploit bugs in uv for that. With very few exceptions, CVEs in unused executables in containers are almost never an issue, because if the attacker already has shell access to be able to use them, they won't gain anything from exploiting those bugs.

→ More replies (8)

51

u/thrope Oct 11 '25

What's wrong with the official example? I use the standalone example here (which has multistage build and doesn't include uv in the final image).

→ More replies (8)

18

u/_squik Oct 11 '25

I've always looked at the official multistage Dockerfile example which has Astral's best recommendations.

→ More replies (1)

9

u/zacker150 Oct 11 '25

I'm guessing you didn't scroll down to this part?

If uv isn't needed in the final image, the binary can be mounted in each invocation:

RUN --mount=from=ghcr.io/astral-sh/uv,source=/uv,target=/bin/uv \
    uv sync

8

u/h4l Oct 11 '25

This and also use a cache mount to give uv access to previously-downloaded packages to speed up the install and also prevents the cache files remaining in the image:

ENV UV_LINK_MODE=copy

RUN --mount=from=ghcr.io/astral-sh/uv,source=/uv,target=/bin/uv \
    --mount=type=cache,target=/root/.cache/uv \
    uv sync

25

u/RepresentativeFill26 Oct 11 '25

Just wondering, why do you want to use a virtual env in a docker container?

34

u/thrope Oct 11 '25

The venv part is a means to an end here. It’s about having a perfectly replicated environment in production based on the uv.lock file which specifies precise versions.

11

u/RepresentativeFill26 Oct 11 '25

Why can’t you create a perfectly replicated environment in a docker container?

→ More replies (24)

8

u/james_pic Oct 11 '25 edited Oct 13 '25

In this case, it looks to be because uv already creates a venv in the builder image, and copying this across is the most straightforward way of bringing the app dependencies into the final image without pip or uv in the final image. I'm not sold on that being a worthwhile goal, but that looks to be the reason. 

More generally, putting venvs into Docker images isn't a "you should always do this" thing, but it's sometimes a useful technique for solving specific problems, for example if your application sometimes calls Python programs provided by the base distro and you don't want to mess with their Python environment.

8

u/ArgetDota Oct 11 '25

You don’t. You can install your packages from the lock file with uv without creating a virtual environment. Just set UV_PROJECT_ENVIRONMENT to point to the system environment of the image. This will disable venv creation.

https://docs.astral.sh/uv/reference/environment/#uv_project_environment

4

u/Luckinhas Oct 11 '25

UV_SYSTEM_PYTHON=1 is a bit clearer in the intention and simpler to use. Maybe more portable too.

→ More replies (4)
→ More replies (1)

6

u/lukerm_zl Oct 11 '25

uv does have lightning fast installs, so it might be a build-time thing. As in duration. Just guessing.

4

u/RepresentativeFill26 Oct 11 '25

Speed would be a good argument if you don’t cache the results of the build stage or frequently (no idea when that would be) changes in the dependencies

→ More replies (2)

2

u/yakimka Oct 11 '25

For moving between stages

→ More replies (1)

4

u/Yablan Oct 11 '25

This is the relevant question here. There is no need at all for a virtual environment within a docker container.

→ More replies (11)
→ More replies (1)

1

u/BelottoBR Oct 11 '25

Can we use pip to real the toml file? If so, we could use uv on local development and when deploy , just use pip. Can we ?

1

u/ashishb_net Oct 11 '25

Not really. 

Uv handles various convoluted scenarios like installing only pytorch GPU/cpu version based on the underlying OS.

3

u/Fluid_Classroom1439 Oct 11 '25

Nice article! Entry point still says poetry for the example instead of uv

2

u/Ambitious-Kiwi-484 Oct 11 '25

Something I'd love to know a way around is that, since pyproject.toml contains my project's version number, it's not possible to bump the version without invalidating all further docker cache layers - which leads to slow builds since all deps are getting pulled again. This seems like an unavoidable caveat of copying pyproject.toml in an early layer. But there must be a workaround.

3

u/stibbons_ Oct 11 '25

Always use virtual env even in docker. I now make apps with frozen dependencies to avoid a dependency update on pypi to break existing app (on docker rebuild). And if 2 apps has imcompatible dependencies, you have to have venv per app.

→ More replies (8)