Should you use uv’s managed Python in production?
The uv
Python packaging tool provides fast replacements for tools like pip
, and a variety of developer experience improvements.
Unlike most Python packaging tools, uv
doesn’t require Python to be installed to use it.
Building on that ease of installation, one of its interesting and useful features is the ability to install Python for you.
As a developer, this is great: if you need a version of Python you don’t have installed, uv
can install it for you (transparently, by default!).
Imagine for some reason you need the long-defunct Python 3.7.
You can easily install it like so:
$ python3.7
python3.7: command not found
$ uv run --python=3.7 python
... uv downloaded 3.7 for me ...
Python 3.7.9 (default, Aug 23 2020, 00:57:53)
[Clang 10.0.1 ] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
The next time you use uv
to run Python 3.7 it will use the cached download.
The ability to install Python with uv
adds interesting possibilities for production packaging.
For example, you can use an Ubuntu 24.04 base Docker image, download uv
, and rely on uv
to trivially install any Python version.
Which is to say, you won’t be limited to the versions Ubuntu packages for you.
But do you want to use this particular version of Python in production?
In this article we’ll look into the implications of using uv
’s Python, and in particular:
- Where this version of Python comes from.
- Portability and compatibility: Can you use this Python on various versions of Linux? Will it run your software correctly?
- Performance: Are you losing speed by using this version?
- Security: Will you get security updates if you use this approach?
Where uv’s Python executables comes from
If you dig into the documentation, you will learn that (as of the time of writing):
uv … uses pre-built third-party distributions from the
python-build-standalone
project.python-build-standalone
is partially maintained by the uv maintainers …
This project, originally started by Gregory Szorc, “produces self-contained, highly-portable Python distributions” according to its documentation.
Portability
For production code that runs on Linux, the main constraint of portability of Linux executables is the glibc (standard C library) version it was compiled against. An executable compiled against glibc 2.32 likely won’t work on an older Linux distribution with, say, glibc 2.27.
We can check versions of glibc that various symbols in the Python distribution depend on using the nm
tool, which lists the symbols used by an executable or shared library.
These symbols can have versions in them, and we in particular care about the glibc version:
$ nm ~/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/libpython3.12.so.1.0 | grep -o 'GLIBC.*$' | sort -u
GLIBC_2.10
GLIBC_2.11
GLIBC_2.13
GLIBC_2.14
GLIBC_2.15
GLIBC_2.17
GLIBC_2.2.5
GLIBC_2.3
GLIBC_2.3.2
GLIBC_2.3.3
GLIBC_2.3.4
GLIBC_2.4
GLIBC_2.5
GLIBC_2.6
GLIBC_2.7
GLIBC_2.9
Glibc 2.17 is the newest version that’s required, and that was released in 2012, so I’d expect any modern Linux distribution to work.
Compatibility
Because of the way python-build-standalone
is built, it has a number of documented quirks.
None of them seem particularly relevant to a production Python application running on Linux.
Performance
There are different ways to build the Python interpreter, and these can have performance implications.
Using the pystone.py
benchmark, we can run Python 3.12 three different ways:
- Ubuntu 24.04’s Python 3.12 build.
- The Python executable
uv
installs. - The “official” Docker image for Python, which runs on Debian and compiles its own version of Python.
I used hyperfine
to run the benchmark multiple times to reduce noise.
Respectively:
$ ./hyperfine "python3.12 pystone.py 1_000_000"
$ uv --python-preference=only-managed run --python=3.12 ./hyperfine "python pystone.py 1_000_000"
$ docker run --privileged -v $PWD:/code python:3.12-slim /code/hyperfine "python /code/pystone.py 1_000_000"
Here’s how long it took to run each one:
Version of Python | Mean run time (lower is better) |
---|---|
Ubuntu 24.04 LTS | 1.395 sec |
uv / python-build-standalone |
1.449 sec |
python:3.12-slim Docker image |
1.583 sec |
The uv
-provided Python executable is slower than the one shipped by Ubuntu 24.04 LTS, but it’s faster than the “official” Docker image.
Security
Making sure your Python executable is secure
You want to use a version of Python with important security patches applied. This depends on multiple aspects of how the project shipping you your Python executable operates:
- People: If there’s only one person running the project, and some manual intervention is part of the process of releasing new versions, you won’t get security updates if that one person happens to be sick, or on vacation.
- Process: Is there a process in place to notice new releases and build them?
For projects like long-term support Linux distributions, or the python
Docker image, the presumption is that there are multiple people available for emergencies, and well-defined processes to ensure security updates go out.
As far as people go, multiple people have the ability to merge PRs into the python-build-standalone
repository, so there’s no one person who is the bottleneck.
And when Python 3.12.6 came out on Sep 6, 2024, a PR was opened the next day to update the project.
Python 3.12.7 was released Oct 2, 2024, and was also available within days.
Overall this looks good.
OpenSSL and other dependencies
If you’re using Python’s built-in support for TLS, you’re typically using the OpenSSL provided by your Linux distribution.
For example, that’s the case with the Python packaged by Ubuntu 24.04.
We can use the ldd
tool to see where the OpenSSL shared library is coming from:
$ ldd /usr/lib/python3.12/lib-dynload/_ssl.cpython-312-x86_64-linux-gnu.so | grep libssl
libssl.so.3 => /lib/x86_64-linux-gnu/libssl.so.3 (0x00007593ba463000)
And similarly for the python:3.12-slim
Docker image:
$ docker run -it python:3.12-slim bash
root@5eccf5d9c9e5:/# ldd /usr/local/lib/python3.12/lib-dynload/_ssl.cpython-312-x86_64-linux-gnu.so | grep libssl
libssl.so.3 => /lib/x86_64-linux-gnu/libssl.so.3 (0x000076d3e7815000)
That means you can rely on the Linux distribution to provide OpenSSL security updates.
If you’re using python-build-standalone
via uv
, however, OpenSSL is not coming from the Linux distribution.
Instead, OpenSSL appears to be linked in statically, and is shipped together with the Python distribution uv
downloads for you.
That means you’re reliant on python-build-standalone
to ship OpenSSL security updates.
When I first started writing this article on October 10, 2024:
- OpenSSL 3.0.15 was released September 3rd, with two security updates.
- Despite this, the latest version of Python 3.12 in
python-build-standalone
, built on Oct 2, 2024, shipped with OpenSSL 3.0.14. You can runimport _ssl; print(_ssl.OPENSSL_VERSION)
in a Python prompt to check the OpenSSL version.
So at the time there didn’t appear to be a mechanism to update OpenSSL.
I reached out the maintainers, and as I write this a few days later, they are releasing versions that use OpenSSL 3.0.15. They also mentioned wanting to create automated processes to do new builds as new versions OpenSSL (and other dependencies) come out. Assuming the latter happens, security updates for OpenSSL and other dependencies also seem like something you can rely on.
If you’re using Docker, you will still need to make sure that you rebuild the image regularly without caching to ensure secure images.
One thing I’d worry about a little as far as security is developer environments when using
uv
. At the moment (October 2024) security updates to the Python version shipped byuv
don’t update virtualenvs thatuv
already created, for example.
Should you use uv
’s Python in production?
I’m writing this on October 15th, 2024.
Last week I would’ve said you probably shouldn’t be using uv
’s Python in production, because you wouldn’t be getting security updates to OpenSSL.
This week, I would tentatively say that it’s fine.
This makes me a little uncomfortable, because there may well be other issues I haven’t thought of, and uv
is still very new.
In the short term, then, I would suggest doing your due diligence based on the risk profile of your particular application.
In the long term, I trust the uv
maintainers to get things right by default.