Charlie Marsh and the brillant guys at Astral have helped the python ecosystem a lot with ruff and now they have released a new tool: uv.
It is intended as a drop-in replacement of pip. At the moment, it supports dependency resolution and installation. Compared to pip, it is a lot faster. I have observed up to 10-15x faster dependency installations (with no caching) for medium-sized projects.
Using uv to speed up Docker builds
Another nice benefit is that his can speed up your Docker build. You can even use uv without having to install it into your final Docker image using a multi-stage build.
Multi-stage builds
A typical multi-stage build for a Python projects works like this:
- We use a build image that has all the necessary tools to build certain python packages.
- Our final image is a slimmed down version of the build image that contains no build dependencies. We copy over the compiled dependencies from the build image to our final image.
Example: Azure Function App
Let’s look at this example of a Dockerfile for an Azure Function App.
(Thank you to @ryxcommar for the Dockerfile commands that install uv from his blog article.)
Let’s break this down assuming you have your Function App files in the directory for which you are calling docker buildx build .
:
- First, we use the
python:3.11
image as our build image. It is close enough to our base image for the final image. - We install uv and create a virtual environment at
/home/packages/.venv
usinguv venv /home/packages/.venv
. - We install our dependencies from the
requirements.txt
file that we copied over.
This is it for the build image. Now, we need a final image:
- Our final image is based on the image necessary for the Python Function App
mcr.microsoft.com/azure-functions/python:4-python3.11
. - Then, we set some environment variables. Here, the important ones are the
PATH
and theVIRTUAL_ENV
to make sure our virtual environment is picked up by the system. - Then, we copy over the complete virtual environment from
/home/packages/.venv
in the build image to our final image. - Finally, we copy all the files necessary for the Function App into our image.
Example: Python app
A more general example for a Dockerfile of a Python app could be this:
In this example, we use the python:3.12
image as the build image and then the slimmed down version python:3.12-slim-bookworm
for the final app image.
You will need to add your ENTRYPOINT
to make this work but the dependencies will be installed and calling python
will use your virtual environment.
If you want to learn more about multi-stage docker builds, I can highly recommend this article about multi-stage docker builds for python and also this post about using virtual environments in dockerfiles from Itamar Turner-Trauring.