uv Python Package Manager
Article written by humans, image generated by bing.
uv is a new package manager that takes over the python ecosystem in a storm. For a nice introduction see the official Getting Started guide.
uv can run your file with environment variables
uv run
executes the python file with the virtual environment, you do not need to activate virtual environment anymore.
More importantly it also supports environment variables files with the --env-file
parameter, which removes the need for the python-dotenv package or additional tools like dotenvx.
uv run --env-file .env main.py
Note that uv run
is quite the magic command, it will run a sync and even download python if it is missing.
uv and Docker
In the pipeline we use the following uv commands and settings. See full Dockerfile at the end.
export UV_PYTHON_DOWNLOADS=never
export UV_COMPILE_BYTECODE=1
uv sync --locked --no-dev
uv run --no-sync main.py
- UV_PYTHON_DOWNLOADS=never: uv would download python if it is missing, we generally do not want that. Should you use uv’s managed Python in production?
- UV_COMPILE_BYTECODE=1: uv will compile Python source files to bytecode when running
uv sync
. --locked
: uv will throw an error if the lockfileuv.lock
is not up-to-date. Very important.--no-dev
: uv will not install the dependencies from the development group.--no-sync
or UV_NO_SYNC=1 :uv run
will not run the sync step again.
uv is a single 27MB executable, which makes it easy to install and download.
Scanning for Security Vulnerabilities
Vulnerability scanners like osv-scanner do not yet support uv.lock github issue, to work around this we manually have to export a requirements file
uv export --output-file requirements.txt --quiet
osv-scanner requirements.txt
example output if something is found
╭─────────────────────────────────────┬──────┬───────────┬─────────┬─────────┬──────────────────╮
│ OSV URL │ CVSS │ ECOSYSTEM │ PACKAGE │ VERSION │ SOURCE │
├─────────────────────────────────────┼──────┼───────────┼─────────┼─────────┼──────────────────┤
│ https://osv.dev/PYSEC-2022-202 │ 7.4 │ PyPI │ pyjwt │ 2.0.0 │ requirements.txt │
│ https://osv.dev/GHSA-ffqj-6fqr-9h24 │ │ │ │ │ │
╰─────────────────────────────────────┴──────┴───────────┴─────────┴─────────┴──────────────────╯
Updating Packages
To list outdated packages you can run the command:
uv pip list --outdated
Similar to the vulnerabilities scanners, the pyproject.toml is not yet supported in many third party updating tools. For this you have to use the exported requirements.txt again.
Recommendation
- Pipeline should require that packages are pinned in pyproject.toml.
- PIpeline checks that
uv sync --locked
does succeed. - The requirements.txt is up to date after each change until tooling supports uv.lock.
Docker cheatsheet
.dockerignore
must contain .venv
# build container
docker build -t uv-example -f Dockerfile .
# run container and cleanup after
docker run -it --rm uv-example
# shell into container for debugging
docker run -it --rm --entrypoint /bin/bash uv-example
Dockerfile using virtual environment
For security, we run the script under a dedicated user ID and not root (id=0).
FROM python:3.13 AS builder
WORKDIR /app
RUN pip install uv
ENV UV_PYTHON_DOWNLOADS=never \
UV_COMPILE_BYTECODE=1 \
UV_NO_SYNC=1
# Copy the requirements file into the container
COPY uv.lock pyproject.toml /app/
# Install the dependencies
RUN /usr/local/bin/uv sync --locked --no-dev
FROM python:3.13 AS base
# Create a dedicated user to run the app
RUN groupadd --gid=1654 app \
&& useradd -l --uid=1654 --gid=1654 --shell /bin/false app \
&& install -d -m 0755 -o 1654 -g 1654 "/app"
# UID of the non-root user 'app'
ENV APP_UID=1654
WORKDIR /app
COPY --chown=$APP_UID:$APP_UID . /app/
COPY --from=builder --chown=$APP_UID:$APP_UID /app /app
ENV VIRTUAL_ENV=/app/.venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
USER $APP_UID
CMD ["python", "main.py"]
Dockerfile using uv run
FROM python:3.13
RUN groupadd --gid=1654 app \
&& useradd -l --uid=1654 --gid=1654 --home-dir /app --shell /bin/false app \
&& install -d -m 0755 -o 1654 -g 1654 "/app"
# UID of the non-root user 'app'
ENV APP_UID=1654
WORKDIR /app
RUN pip install uv
ENV UV_PYTHON_DOWNLOADS=never \
UV_COMPILE_BYTECODE=1 \
UV_NO_SYNC=1
# the lock file is copied first to avoid re-installing dependencies
COPY uv.lock pyproject.toml /app/
RUN uv sync --locked --no-dev
COPY . .
USER $APP_UID
CMD ["uv", "run", "main.py"]