n1ro°
RU

Текст и данные · Инструкция

uv sync --locked vs --frozen в CI: в чём разница

uv sync --locked vs --frozen в CI различаются не тем, какой набор версий устанавливается из `uv.

Редакция N1RO · Проверено

Короткий ответ

Для обычного CI после полного checkout чаще нужен `uv sync --locked`: он не позволяет незаметно пересчитать lockfile и одновременно ловит рассинхронизацию `pyproject.toml`/`uv.lock`. `--frozen` полезен в специальных Docker-слоях или частичном workspace-контексте, где проверить актуальность lockfile пока невозможно, но вы сознательно хотите использовать уже зафиксированные версии.

Что uv делает при обычной синхронизации

В проектном workflow uv поддерживает lockfile и environment как связанные состояния. Обычный `uv sync` при необходимости обновляет lockfile, а затем синхронизирует окружение; для локальной разработки это удобно, но в CI часто нежелательно, потому что pipeline должен проверять зафиксированное состояние репозитория, а не молча создавать новое. Опция `--locked` меняет это правило: uv проверяет, требуется ли обновление lockfile, и вместо изменения завершает команду ошибкой. `--frozen` идёт ещё дальше в другом направлении — отключает проверку свежести lockfile и использует записанное состояние как есть. То есть `frozen` не является «ещё более строгим locked»; наоборот, он пропускает проверку согласованности с metadata. Это ключевой смысловой момент при выборе флага. Если ваша цель — обнаружить забытый `uv lock` после изменения зависимостей, нужен `--locked`, а не `--frozen`.

Важно: Название `--frozen` легко трактовать как «строже locked», но в uv оно означает «не проверять, нужно ли обновлять lockfile». Для контроля рассинхронизации в полном репозитории используйте `--locked`.

Надёжный шаблон для CI

  1. Закоммитьте `uv.lock`: Lockfile должен быть частью репозитория, если pipeline рассчитывает на воспроизводимое project environment.
  2. Получите полный project metadata: В обычном CI checkout должен содержать `pyproject.toml`, workspace members и файлы, влияющие на dependency metadata.
  3. Проверьте lockfile: Запустите `uv sync --locked` или отдельно `uv lock --check`, если сначала нужна только проверка актуальности.
  4. Запустите тесты в синхронизированном окружении: После успешного locked sync выполняйте lint/test/build. Ошибка lockfile должна останавливать pipeline до запуска тестов.
  5. Исправляйте рассинхронизацию локально: Если locked-команда падает после изменения dependencies, обновите lockfile явной командой в рабочей ветке, проверьте diff и закоммитьте его.

Предупреждение: Не заменяйте ошибку `--locked` автоматическим `uv lock` прямо в CI, если цель pipeline — проверить репозиторий. Иначе забытый lockfile снова станет скрытым изменением build-среды.

Почему `--frozen` нужен в Docker workspace

Официальный Docker guide uv показывает важный edge case для workspace. Чтобы максимизировать cache, Dockerfile сначала копирует `uv.lock` и корневой `pyproject.toml`, но ещё не копирует все `pyproject.toml` workspace members. В этот момент uv физически не располагает полной metadata, необходимой для проверки lockfile. Поэтому ранний слой может выполнять `uv sync --frozen --no-install-workspace`: он использует уже существующий lockfile и устанавливает внешние зависимости, не пытаясь валидировать состояние неполного workspace. После того как Dockerfile копирует все workspace members и исходники, guide переключается на `uv sync --locked`. Это хорошая модель мышления: `frozen` — сознательный инструмент для частичного контекста, `locked` — финальная проверка полного контекста. Если просто оставить `--frozen` и на последнем этапе, можно потерять сигнал о том, что project metadata и lockfile расходятся.

Exact sync и лишние пакеты

У `uv sync` есть ещё одно поведение, которое иногда ошибочно приписывают locked/frozen: по умолчанию project sync является exact, то есть удаляет из окружения лишние пакеты, не предусмотренные lockfile. Это отдельная ось поведения. Флаги `--locked` и `--frozen` отвечают за отношение к свежести lockfile, а не за то, должен ли environment быть exact. Поэтому при диагностике CI разделяйте вопросы. Если команда падает с сообщением о stale lockfile, разбирайтесь с metadata и lock. Если в окружении исчез пакет, установленный вручную, это уже следствие exact sync или конфигурации environment, а не различия locked/frozen. Такой раздел помогает не лечить неправильную проблему заменой флага. Аналогично `uv run` может иметь иное поведение синхронизации по умолчанию, поэтому pipeline лучше строить явно, а не рассчитывать на побочный sync команды запуска.

Практические сценарии выбора

Сценарий первый: обычный GitHub Actions/GitLab CI, полный checkout, нужно гарантировать, что developer не забыл обновить lockfile. Выберите `uv sync --locked`. Сценарий второй: отдельный validation job, которому окружение ещё не нужно — используйте `uv lock --check`, затем install/sync на следующем шаге. Сценарий третий: Docker workspace и ранний dependency-cache layer, где ещё не скопированы manifests всех members. Здесь `uv sync --frozen --no-install-workspace` соответствует официальной схеме, но после полного `COPY` должен появиться `uv sync --locked`. Сценарий четвёртый: аварийный build из существующего lockfile при временно неполной metadata. `--frozen` может быть осознанным выбором, но его стоит задокументировать, потому что он убирает freshness-signal. Везде, где полный проект доступен, locked обычно лучше выражает требование «репозиторий и lockfile должны совпадать».

Совет: Если вы используете `--frozen` в CI не из-за частичного контекста, оставьте комментарий, почему freshness-check намеренно отключён. Иначе будущий maintainer почти наверняка примет это за обычную строгую проверку.

Как разбирать падение `uv sync --locked` в CI

Если `uv sync --locked` завершился ошибкой после изменения зависимостей, сначала сравните `pyproject.toml`, workspace manifests и `uv.lock`, а не переключайте job на `--frozen`. Ошибка означает, что uv считает lockfile неактуальным и отказался его менять. В рабочей ветке запустите обычный `uv lock` или другой предусмотренный проектом процесс обновления lockfile, изучите diff и закоммитьте результат вместе с изменением dependency metadata. Если локально `--locked` проходит, а в CI нет, проверьте полноту checkout: для workspace должны быть доступны manifests всех участников, иначе проверка согласованности может выполняться в другом контексте. В Docker ранний cache-layer является отдельным случаем: официальный guide специально использует `--frozen`, когда часть workspace metadata ещё не скопирована, а после полного `COPY` возвращается к `--locked`. Поэтому лечить любое CI-падение заменой locked на frozen означает убрать полезную проверку, а не исправить рассинхронизацию.

Проверка CI-конфигурации

  • `uv.lock` находится в репозитории
  • Полный `pyproject.toml`/workspace metadata доступен перед финальным sync
  • Финальный CI sync использует `--locked`, если нужна проверка актуальности
  • `--frozen` применяется только там, где пропуск freshness-check осознан
  • Docker после копирования всех members выполняет финальную locked-проверку
  • Pipeline не перезаписывает lockfile молча при обычной проверке

Что учитывать

Официальный Docker guide uv показывает важный edge case для workspace. Чтобы максимизировать cache, Dockerfile сначала копирует `uv.lock` и корневой `pyproject.toml`, но ещё не копирует все `pyproject.toml` workspace members. В этот момент uv физически не располагает полной metadata, необходимой для проверки lockfile. Поэтому ранний слой может выполнять `uv sync --frozen --no-install-workspace`: он использует уже существующий lockfile и устанавливает внешние зависимости, не пытаясь валидировать…

Источники и проверка

Фактическая часть сверена по первичным источникам (в т.ч. Locking and syncing). Пример и формулировки — редакция N1RO на 2026-09-21.