astral-sh / uv

An extremely fast Python package and project manager, written in Rust.
https://docs.astral.sh/uv
Apache License 2.0
20.27k stars 602 forks source link

Docker: feedback on docs and workflow with frozen/locked dependencies and `--require-hashes` #6451

Open bofm opened 3 weeks ago

bofm commented 3 weeks ago

Hello Astral team! Thank you for your work on Ruff and UV! It makes a huge difference.

Please clarify the best way to install the locked dependencies and validate by the hashes when building a Docker image.

Key points when building a Docker image of an app:

Problems:

Workaround

This is what I ended up with right now with uv 0.3.1.

FROM python:3.12-slim as uv
# Installing via pip because there is no (yet) docker image for armv7 
RUN --mount=type=cache,target=/root/.cache pip install -U uv

FROM python:3.12-slim

RUN mkdir /tmp/app
WORKDIR /tmp/app

COPY requirements.lock .
RUN --mount=type=cache,target=/root/.cache \
    --mount=from=uv,source=/usr/local/bin/uv,target=/bin/uv \
#  !!!! this grep hack !!!!
    cat requirements.lock | grep -vE '^-e' > requirements.txt \ 
    && uv pip install --system --require-hashes --link-mode copy -r requirements.txt

What it should be like

I felt like I need something like uv sync —system --frozen-only which would not create a venv and would only use uv.lock to install the dependencies.

Versions

charliermarsh commented 3 weeks ago

There's something like this brewing in https://github.com/astral-sh/uv/pull/6398, designed for this very purpose (with discussion here: https://github.com/astral-sh/uv/issues/4028). It won't solve the --system part yet, that will come separately.

charliermarsh commented 3 weeks ago

(Sorry, can't give a full reply now, I just wanted to point you there since it creates uv sync --frozen --no-locals to the project itself.)

charliermarsh commented 3 weeks ago

You can run uv sync --frozen today though to install from only uv.lock.

bofm commented 3 weeks ago

You can run uv sync --frozen today though to install from only uv.lock.

Yes, but this way it creates a venv and installs in there. The desired way is to install into the system python in the docker container.

UPD1: and it also requires the pyproject.toml to be copied into the workdir.

charliermarsh commented 3 weeks ago

Yeah the --no-locals from that PR would make it such that we don't require the pyproject.toml.

We don't yet support installing into system Python but I suspect we will soon for this reason.

bofm commented 3 weeks ago

There is a good point in https://github.com/astral-sh/uv/issues/4028#issuecomment-2305322593

uv sync is not a command designed for application install

What would be left after --no-locals is merged and the project deps are installed is that we'd still need to install the project itself (currently uv pip install .) which mixed the two worlds: pip and non-pip. We got ourselves another P vs NP problem.

charliermarsh commented 3 weeks ago

Not quite -- you can see the example in the PR:

# Copy the lockfile into the image
ADD uv.lock /app/uv.lock

# Install remote dependencies
WORKDIR /app
RUN uv sync --frozen --no-locals

# Copy the project into the image
ADD . /app
WORKDIR /app

# Sync the remaining dependencies
RUN uv sync --frozen

The benefit here is that you can create a cacheable layer with your project's dependencies.

bofm commented 3 weeks ago

Nice! What's still missing? Omitting venv creation?

albertotb commented 3 weeks ago

I could not find any issue that tracks the feature request of omitting venv creation so I will share another use case I found here (besides the Docker one).

Context

Mypy is notoriusly difficult to run correctly using pre-commit, since pre-commit uses an isolated env for every hook but mypy wants to have all your dependencies installed. To get around that, one can use language: system to omit venv creation and use the project's venv (with mypy and all dependencies installed):

- repo: https://github.com/pre-commit/mirrors-mypy
  rev: v1.6.1
  hooks:
    - id: mypy
      language: system

Problem

In CI you need to install the dependencies to run mypy. If using uv, currently it looks something like this:

jobs:
  pre-commit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version-file: ".python-version"
      - name: Set up uv
        run: curl -LsSf https://astral.sh/uv/0.3.1/install.sh | sh
      - name: Install dependencies
        run: uv sync --dev --frozen
      - uses: pre-commit/action@v3.0.1

However, that does not work because pre-commit uses the system Python and dependencies (and mypy) are installed into a venv.

Current solutions

As a workaround, you can either run pre-commit manually or add an extra step before pre-commit that adds the venv to the path and installs pip (needed for pre-commit to install the tools into the isolated venvs):

      - run: |
          echo "$PWD/.venv/bin" >> $GITHUB_PATH
          uv pip install pip

The solution would be straightforward if a --system flag existed in uv sync.

EDIT: I've just found issue https://github.com/astral-sh/uv/issues/5964 that seems to be asking exactly for this

inflation commented 3 weeks ago

Furthermore, some Python libraries with native extensions must be built from sources, which require a compiler (or more than one, e.g. nvcc, rustc, etc). We want to keep that in the builder image only.

For now, we copy the .venv folder and have a workaround to fix the symlink:

RUN uv python install 3.10
RUN ln -sf $(uv python find 3.10) $HOME/$DIR_NAME/.venv/bin/python 

It would be great to have a mechanism to "bundle" all the dependencies and python itself, so we could copy it to the runtime image and run the app without uv.