Перед началом
Что такое UV?
UV - менеджер пакетов Python на основе Rust, разработанный командой Astral. Цель - обеспечить быструю установку, разрешение зависимостей и создание виртуальных окружений.
uv заменяет собой такие инструменты как:
pippip-toolspipxpoetrypyenvtwinecondavirtualenv
Его стоит использовать как минимум из-за скорости работы.

Основные особенности
- В 10–100 раз быстрее, чем pip.
- Обеспечивает комплексное управление проектами с помощью универсального файла блокировки.
- Запускает скрипты с поддержкой встроенных метаданных зависимостей.
- Устанавливает и управляет версиями Python.
- Запускает и устанавливает инструменты, опубликованные в виде пакетов Python.
- Включает в себя интерфейс, совместимый с pip, для повышения производительности с помощью знакомого CLI.
- Поддерживает рабочие пространства в стиле Cargo для масштабируемых проектов.
- Эффективно использует дисковое пространство благодаря глобальному кэшу для дедупликации зависимостей.
Установка UV
MacOS/Linux
Для установки UV на MacOS или Linux используется команда:
curl -LsSf https://astral.sh/uv/install.sh | shИли:
wget -qO- https://astral.sh/uv/install.sh | shИли с указанием конкретной версии:
curl -LsSf https://astral.sh/uv/0.9.26/install.sh | shТак же для MacOS доступна версия в Brew или MacPorts:
brew install uv
sudo port install uvВ Linux зависит от дистрибутива.
Windows
Можно установить через winget:
winget install --id=astral-sh.uv -eИли Scoop:
scoop install uvИли
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"Автодополнение
Чтобы включить автодополнение в оболочке uvx, выполните одно из следующих действий
uv - bash
echo 'eval "$(uv generate-shell-completion bash)"' >> ~/.bashrcuvx - bash
echo 'eval "$(uvx --generate-shell-completion bash)"' >> ~/.bashrcuv - zsh
echo 'eval "$(uv generate-shell-completion zsh)"' >> ~/.zshrcuvx - zsh
echo 'eval "$(uvx --generate-shell-completion zsh)"' >> ~/.zshrcuv - fish
echo 'uv generate-shell-completion fish | source' > ~/.config/fish/completions/uv.fishuvx - fish
echo 'uvx --generate-shell-completion fish | source' > ~/.config/fish/completions/uvx.fishuv - powershell
if (!(Test-Path -Path $PROFILE)) {
New-Item -ItemType File -Path $PROFILE -Force
}
Add-Content -Path $PROFILE -Value '(& uv generate-shell-completion powershell) | Out-String | Invoke-Expression'uvx - powershell
if (!(Test-Path -Path $PROFILE)) {
New-Item -ItemType File -Path $PROFILE -Force
}
Add-Content -Path $PROFILE -Value '(& uvx --generate-shell-completion powershell) | Out-String | Invoke-Expression'Удаление
Очистите кэш:
uv cache clean
rm -r "$(uv python dir)"
rm -r "$(uv tool dir)"Удалите исполняемые файлы:
rm ~/.local/bin/uv ~/.local/bin/uvxrm $HOME\.local\bin\uv.exe
rm $HOME\.local\bin\uvx.exe
rm $HOME\.local\bin\uvw.exeУправление версиями Python
# Установка нужной версии
uv python install ...
# Список версий
uv python list
# Поиск установленной версии
uv python find ...
# Привязка проекта к версии
uv python pin ...
# Удаление версии
uv python uninstall ...Выполнение кода
# Запуск скрипта
uv run ...inline зависимости
Допустим, вы пишете какой-либо скрипт, но не хотите для него создавать отдельное виртуальное окружение или какая-то зависимость вам нужна только здесь и вы не будете использовать ее где-то еще, или вдруг вам нужно отправить один файл кода своему товарищу, но вы не хотите плодить лишние файлы. UV может решить эту проблему.
uv add --script имя-файла зависимости
# или
uv add --script имя-файла --requirements requirements.txtПример:
uv add --script 123.py 'requests<3' 'rich'Что это изменило? Теперь в начале файла прописаны зависимости:
# /// script
# requires-python = ">=3.14"
# dependencies = [
# "requests<3",
# "rich>=14.2.0",
# ]
# ///И этот скрипт из любой папки, можно запустить командой и для этого не нужно создавать виртуальное окружение:
uv run 123.py
Предупреждая все заявления о том, что это костыли и “говнокод” сошлюсь на стандарт PEP723 - Строковые метаданные скрипта.
Предупреждение
Применяется это в случаях когда скрипт состоит из одного файла, чтобы не плодить лишние файлы и виртуальные окружения. Этот стандарт понимают
pipx,poetryиuv, для них он и был придуман.
Аналогично можно и удалять зависимости:
uv remove --script файл зависимость Зачем это нужно?
1. Борьба с «хламом» в системе
Раньше разработчики часто устанавливали библиотеки глобально (pip install requests), что со временем приводило к конфликтам версий и поломке системных утилит. PEP 723 позволяет держать систему чистой: зависимости устанавливаются во временный кэш только на время работы скрипта.
2. Принцип «Один файл — один инструмент»
Представьте, что вы написали скрипт для парсинга логов и хотите передать его коллеге.
- Раньше: Нужно передать
.pyфайл,requirements.txtи инструкцию по созданию venv. - С PEP 723: Вы передаете один
.pyфайл. Коллега пишетuv run script.py, и всё работает. Воспроизводимость спустя годы Вы можете указать точные версии:requests==2.31.0. Когда вы запустите этот скрипт через 2 года, он не сломается из-за обновления библиотек, потому что менеджер пакетов создаст именно то окружение, которое было задумано.
Работа с проектами
В основе работы uv лежит файл pyproject.toml. Работа с файлом происходит через команды указанные ниже.
uv init
Создает проект в текущей или указанной папке. Если указать после команды директорию, то будет создана директория.
uv init helloworld # Создаст проект и папку helloworld для негоФайлы проекта:
.git # Репозиторий
.gitignore
.python-version # Тут просто написана версия python
README.md
pyproject.toml # Главный файл проекта
main.py # Шаблон python файлаВообще uv init имеет множество аргументов, но можно работать и без них.
Для полноты статьи разберем аргументы:
| Аргумент | Что делает? |
|---|---|
--name <ИМЯПРОЕКТА> | Задает имя проекта в pyproject.toml, по умолчанию имя папки. |
--description <Описание> | Добавляет краткое описание в pyproject.toml |
--no-desciption | В pyproject.toml не будет поля для описания |
--app | Создает структуру файлов для приложения(по умолчанию) |
--lib | Создает структуру файлов для библиотеки и настраивает сборку библиотеки в зависимость(whl файл) |
--package/--no-package | Разрешает или запрещает сборку проекта в пакет |
--bare | Создает только pyproject.toml, без репозитория, файла с версией python, readme.md и шаблона |
--script | Создает файл скрипта с метаданными PEP 723 |
--no-readme | Не создает readme.md |
--no-pin-python | Не создает файл с версией python |
--vsc none | Не создает репозиторий |
--author-from | Откуда взять имя автора(по умолчанию из git, но в целом пока других вариантов нет) |
--build-backend | Что используется для сборки? По умолчанию uv, но можно выбрать poetry, flit, setuptools |
-p/--python | Позволяет указать версию python |
--managed-python/--no-managed-python | Может ли проект использовать версии python установленные не через uv |
--no-python-downloads | Запрещает uv автоматически скачать нужную версию python |
--no-workspace | Если создается проект внутри проекта, этот флаг заставляет игнорировать родительский проект |
-n/--no-cache | Не использовать кеш |
--offline | Запрещает выход в сеть |

uv add и uv remove
Управление зависимостями.
uv add <package>: Добавляет пакет в pyproject.toml и сразу обновляет uv.lock и виртуальное окружение.
--dev: Добавить в блок зависимостей для разработки (тесты, линтеры).--optional <name>: Добавить в опциональные фичи (extra).
uv remove <package>: Удаляет пакет и все его следы из проекта.
Так же имеет ряд флагов.
| Аргумент | Что делает? |
|---|---|
--dev | Устанавливает зависимость для разработки(обычно сюда идут ruff, pytest, flake8, black, mypy и т.д…). Потом при сборке на сервере собираем с флагом --no-dev и эти пакеты не будут установлены. |
--optional <ИМЯ> | Добавляет вариации в проект. Например мы пишем библиотеку и она может работать с postgresql и mysql, добавляем разные вариации и пользователь потом сможет установить не всю библиотеку, а только нужную вариацию. |
--group <ГРУППА> | Позволяет создавать именованные группы зависимостей. Позже можно будет установить только конкретную группу. uv sync --group ГРУППА |
--bounds <lower|exact|major|minor> | Как запишется версия заивимости. - lower >= текущей- exact == текущей- major ограничит обновление до major версии- minor ограничит обновление до минорной версии |
--rev,--tag,--branch | Нужны при установке пакета из git, uv запишет ссылку на коммит/ветку в pyproject.toml |
--no-sync | Допишет библиотеку в список зависимостей, но не будет скачивать |
--locked | Проверяет чтобы добавление пакета не изменило версии текущих пакетов |
--frozen | Обновит pyproject.toml, но не тронет uv.lock |
--script | См. здесь |
uv lock
Создает или обновляет файл uv.lock.
Зачем это нужно: uv.lock содержит точные версии всех зависимостей (включая транзитивные). Это гарантирует, что проект запустится одинаково и на твоем ноутбуке, и на сервере, и у коллеги.
Файл оптимизирован для Git и не вызывает конфликтов при слиянии.
uv sync
Приводит ваше виртуальное окружение в полное соответствие с uv.lock.
| Аргумент | Что делает |
|---|---|
--extra <NAME> | Устанавливает указанную группу опциональных зависимостей (например, uv sync --extra postgres). |
--all-extras | Устанавливает все опциональные зависимости, описанные в проекте. |
--no-extra <NAME> | Исключает конкретный extra-пакет при использовании вместе с --all-extras. |
--no-dev | Production-режим. Не устанавливает пакеты из группы dev (тесты, линтеры). |
--only-dev | Устанавливает только зависимости для разработки, игнорируя основные. |
--group <NAME> | Включает в синхронизацию конкретную именованную группу (например, --group docs). |
--no-group <NAME> | Исключает указанную группу зависимостей из установки. |
--no-default-groups | Не устанавливает группы по умолчанию (обычно это основная dev группа). |
--only-group <NAME> | Устанавливает исключительно указанную группу. Полезно для узких задач. |
--all-groups | Максимальная установка: ставит вообще все группы зависимостей, найденные в проекте. |
--inexact | Важно: По умолчанию uv sync удаляет из .venv всё лишнее. Этот флаг запрещает удаление сторонних пакетов. |
--active | Пытается синхронизировать текущее активированное окружение, а не проектное по умолчанию. |
--no-install-project | Синхронизирует зависимости, но не устанавливает сам текущий проект как пакет. |
--locked | Проверяет, что uv.lock актуален. Если нужно обновить лок — упадет с ошибкой. |
--frozen | Берет текущий uv.lock «как есть» и не пытается его обновлять даже при наличии новых версий. |
--dry-run | «Холостой ход»: покажет, что будет сделано, но не изменит ни файлы, ни окружение. |
--check | Просто проверяет, совпадает ли текущий .venv с лок-файлом (код возврата 0 или 1). |
--output-format | Позволяет получить результат работы в json (удобно для автоматизации). |
--script <FILE> | Синхронизирует окружение не для проекта, а для конкретного одиночного скрипта (PEP 723). |
uv run
Запускает скрипт или команду внутри контекста проекта. Так же не нужно вручную активировать виртуальное окружение (source .venv/bin/activate). Если изменялся pyproject.toml, то uv run сначала обновит окружение, а потом запустит код.
| Аргумент | Что делает |
|---|---|
-m, --module | Запускает указанный Python-модуль (аналог python -m). |
-s, --script | Явно указывает, что переданный путь — это Python-скрипт. |
-w, --with <пакет> | Добавляет пакеты “на лету” только для этого запуска, не записывая их в pyproject.toml. |
--isolated | Создает абсолютно чистое временное окружение специально для этого запуска. |
--env-file <FILE> | Автоматически загружает переменные окружения из .env файла перед запуском. |
--no-env-file | Игнорирует .env файлы, даже если они есть в папке. |
--extra / --all-extras | Включает опциональные зависимости проекта в текущий запуск. |
--no-dev | Запускает команду без установленных инструментов разработки. |
--only-dev | Запуск в окружении, где есть только инструменты разработки. |
--group / --only-group | Позволяет выбрать конкретную группу зависимостей для этого сеанса. |
--all-groups | Устанавливает вообще все группы, описанные в проекте, перед запуском. |
--locked / --frozen | Гарантирует запуск на строго зафиксированных версиях из uv.lock. |
--no-sync | Пропускает проверку и обновление окружения (запуск будет быстрее, если всё уже готово). |
--exact | Перед запуском удаляет из окружения все пакеты, которые не прописаны в проекте. |
--no-project | Запускает команду в изоляции, игнорируя настройки текущего проекта или окружения. |
--package <NAME> | В воркспейсе запускает команду в контексте конкретного подпакета. |
--python-platform | Имитирует запуск на другой ОС (полезно для проверки кроссплатформенности). |
| Фишки: | |
Запуск ПО которого нет в пакете. Пример: uv run ruff check . скачает ruff во временное окружение и запустит команду. |
uv tree
Выводит дерево всех установленных пакетов. Позволяет быстро понять, какой именно пакет притащил с собой ту или иную библиотеку и почему возник конфликт версий.
| Аргумент | Что делает |
|---|---|
--universal | Показывает дерево, независимое от текущей ОС. Отображает все возможные зависимости для всех платформ сразу. |
-d, --depth <N> | Ограничивает глубину дерева. Если проект огромный, uv tree -d 2 поможет увидеть только основные пакеты. |
--package <NAME> | Показывает дерево только для конкретного пакета, а не для всего проекта. |
--prune <NAME> | «Обрезает» указанный пакет из вывода, чтобы он не загромождал дерево. |
--no-dedupe | По умолчанию uv помечает повторные зависимости звездочкой (*). Этот флаг заставляет его рисовать ветки полностью каждый раз. |
--invert | Показывает, какие пакеты зависят от указанного. Ответ на вопрос: «Зачем мне вообще нужен этот пакет?». |
--outdated | Рядом с установленной версией показывает последнюю доступную в PyPI. Удобно для планирования обновлений. |
--show-sizes | Показывает размер сжатых колес (wheels) для каждого пакета. Помогает найти виновников «раздутого» Docker-образа. |
--only-dev / --no-dev | Фильтрует дерево, оставляя или убирая зависимости для разработки. |
--group / --all-groups | Позволяет просмотреть дерево зависимостей для конкретных или всех групп (docs, tests и т.д.). |
--locked / --frozen | Берет данные строго из лок-файла, не пытаясь проверять наличие обновлений в сети. |
--script <FILE> | Показывает дерево для одиночного Python-скрипта (анализирует блок PEP 723). |
--python-version | Фильтрует дерево под конкретную версию Python (некоторые пакеты меняют зависимости в зависимости от версии языка). |
Фишки:
- Поиск лишнего веса
- Просмотр цепочек зависимостей
- Проверка зависимостей при кроссплатформенности (покажет какие макеты на какой оси будут тянуться, флаг
--universal)
uv build и uv publish
Зачем нужны?
Если вы пишете библиотеку
Чтобы другие люди могли написать pip install your-library, вы должны собрать свой код в специальные архивы:
- sdist (Source Distribution): Исходный код. Нужен как резервный вариант.
- wheel: Скомпилированный бинарный формат, который устанавливается мгновенно.
uv buildсоздает эти файлы, аuv publishотправляет их в PyPI.
Если вы пишете приложение
Стандартизация:
Сборка приложения в wheel позволяет устанавливать его в Docker-контейнеры или на сервера одной командой: pip install my_app.whl. Это гарантирует, что все скрипты (entry points) пропишутся в систему правильно.
Внутренние индексы: В крупных компаниях приложения часто публикуются во внутренние репозитории, чтобы другие отделы могли легко использовать их инструменты.
uv build
Эта команда упаковывает ваш проект в папку dist/.
| Аргумент | Что делает |
|---|---|
[SRC] | Путь к папке проекта. По умолчанию — текущая директория. |
-o, --out-dir | Куда сохранить готовые архивы (по умолчанию в папку dist). |
--package / --all-packages | В воркспейсе позволяет собрать либо один конкретный пакет, либо все сразу. |
--sdist | Собрать только архив с исходным кодом (.tar.gz). |
--wheel | Собрать только бинарный дистрибутив (.whl). |
--clear | Удаляет старые файлы из папки dist перед сборкой. Полезно, чтобы не залить в PyPI старую версию по ошибке. |
--no-build-logs | Скрывает технический вывод процесса сборки (оставляет только результат). |
--force-pep517 | Заставляет использовать стандартный механизм сборки вместо оптимизированного внутреннего пути uv. |
uv publish
Эта команда берет файлы из dist/ и отправляет их в мир.
| Аргумент | Что делает |
|---|---|
-u, --username | Логин для PyPI или вашего сервера. |
-p, --password | Пароль или API-токен (рекомендуется использовать токены). |
--publish-url | Позволяет указать свой сервер (например, приватный репозиторий компании). |
--check | (Если доступно) Проверяет метаданные пакета перед отправкой на наличие ошибок. |
--trusted-publishing | Позволяет публиковать пакеты из GitHub Actions без паролей (через OIDC). |
Работа с инструментами
uvx (или uv tool run)
Если uv run нужен для работы внутри проекта, то uvx — это инструмент для запуска Python-утилит снаружи. Это аналог pipx, но работающий в разы быстрее.
Он создает временное изолированное окружение, скачивает туда пакет и запускает команду. После выполнения (или через некоторое время) окружение может быть удалено. Ваш системный Python и локальные проекты остаются абсолютно чистыми.
Зачем это нужно:
- Одноразовые задачи: Нужно быстро проверить код линтером, которого нет в проекте (
uvx ruff check .). - CLI-инструменты: Вы хотите использовать утилиты вроде
black,mypy,httpieилиcowsayглобально, не устанавливая их в каждый проект вручную. - Тестирование: Нужно быстро глянуть, как работает библиотека, не создавая
pyproject.toml.
| Аргумент | Что делает |
|---|---|
--from <PACKAGE> | Если имя команды отличается от имени пакета (например, пакет ansible-core, а команда ansible). |
--with <EXTRA_PACKAGE> | Запустить утилиту вместе с дополнительным пакетом (например, плагином). |
--python <VERSION> | Выполнить инструмент, используя конкретную версию Python. |
--isolated | Игнорировать конфигурацию проекта, даже если вы находитесь в папке с pyproject.toml. |
uv tool install
Устанавливает инструмент навсегда в ваше пользовательское окружение.
В отличие от uv add, который ставит пакет в конкретный проект, эта команда делает инструмент доступным везде в вашей системе. Для каждого инструмента uv создает отдельное скрытое виртуальное окружение.
uv tool list
Показывает список всех инструментов, которые вы установили через uv tool install. Команда выводит названия пакетов, их версии и исполняемые файлы, которые они добавили в систему.
uv tool uninstall
Удаляет установленный инструмент и его личное виртуальное окружение.
uv tool update-shell
Настраивает ваш терминал (bash, zsh, fish) для работы с инструментами. Чтобы установленные через uv tool install команды (типа ruff) работали, путь к папке с их исполняемыми файлами должен быть прописан в системной переменной PATH. Эта команда проверяет ваши конфиги (напр. .zshrc или .bashrc) и добавляет туда нужные строчки, если их нет.
Для любителей pip (Этот блок можно пропустить)
Создание окружений (uv venv)
В отличие от uv sync, которая создает .venv автоматически, uv venv делает это вручную, заменяя стандартный модуль venv.
uv venv: Создает виртуальное окружение в папке.venv.uv venv --python 3.11: Создает окружение с конкретной версией Python.uv venv custom_env: Создает окружение в папке с вашим именем.
Главное преимущество
Скорость.
uvсоздает окружение почти мгновенно (в 10–50 раз быстрее оригинала), используя рефлинки (reflinks) или хардлинки, не копируя файлы физически.
Управление пакетами (uv pip)
Эти команды — прямая и очень быстрая замена стандартному pip. Они работают с текущим активным окружением(source ./.venv/bin/python).
| Команда | Что делает | Особенности |
|---|---|---|
uv pip install <pkg> | pip install | Поддерживает файлы requirements.txt, редактируемые режимы (-e .) и индексы. |
uv pip show <pkg> | pip show | Показывает метаданные: где лежит пакет, его зависимости и лицензию. |
uv pip list | pip list | Выводит таблицу всех установленных пакетов в текущем venv. |
uv pip freeze | pip freeze | Выводит пакеты в формате для requirements.txt. |
uv pip tree | pipdeptree | Крутая фича: Визуальное дерево зависимостей именно для текущего окружения. |
uv pip check | pip check | Проверяет, нет ли конфликтов между установленными пакетами (например, если два пакета требуют разные версии одной библиотеки). |
uv pip uninstall | pip uninstall | Мгновенно удаляет пакет. |
Компиляция и синхронизация (Замена pip-tools)
Это самый мощный блок uv pip. Если вы не используете pyproject.toml, но хотите иметь стабильные сборки, вам нужны эти команды.
uv pip compile
Вы пишете в requirements.in только верхнеуровневые пакеты (например, flask), а команда превращает их в жестко зафиксированный requirements.txt.
Пример: uv pip compile requirements.in -o requirements.txt
Результат: В файле будут все зависимости (включая транзитивные) с конкретными версиями и хэшами для безопасности.
Документация: Поддерживает ограничения (--constraint), разные платформы и работу без интернета.
uv pip sync
Это «силовой» метод установки. Команда берет ваш requirements.txt и делает окружение идентичным ему.
Если в окружении есть лишние пакеты, которых нет в файле — они будут удалены.
гарантирует, что ваша среда разработки на 100% совпадает с тем, что будет на сервере.
Почему стоит использовать uv pip вместо обычного pip?
- Скорость: Все операции происходят в разы быстрее благодаря реализации на Rust.
- Глобальный кэш: Если вы ставили
pandasв одном проекте, во второмuvпросто «подтянет» его из кэша за миллисекунды, не скачивая заново. - Безопасность: По умолчанию проверяет хэши и совместимость зависимостей лучше, чем стандартный
pip. - Умная работа с Python:
uvсам найдет нужную версию Python в системе, вам не нужно указывать полный путь до интерпретатора.
Управление кешем
Кэш — это основа скорости uv. Он хранит скачанные пакеты (wheels), распакованные пакеты и метаданные из PyPI.
Если вы чувствуете, что место на диске тает, выполните:
uv cache prune --ciФлаг --ci более агрессивно удаляет временные файлы, которые обычно нужны только в системах непрерывной интеграции.
| Команда | Что делает | Когда применять |
|---|---|---|
uv cache clean | Полностью удаляет весь кэш. | Если возникли странные ошибки при установке или нужно резко освободить место. |
uv cache prune | Удаляет только «мертвые» записи (старые версии, неиспользуемые пакеты). | Регулярная гигиена раз в месяц. Оставляет актуальные данные, удаляя мусор. |
uv cache dir | Показывает путь к папке кэша в системе. | Если хочешь вручную посмотреть, сколько ГБ занимает uv. |
Важно
Кэш в
uvявляется глобальным для всех ваших проектов. Если вы удалили пакет в одном проекте, но он используется в другом —pruneего не тронет.
Пути хранения (Инспекция системы)
Эти команды помогают понять, куда uv «рассовывает» свои данные. Это критически важно для настройки бэкапов или очистки системы.
uv tool dir
Показывает директорию, где установлены инструменты через uv tool install. Там лежат виртуальные окружения для каждой глобальной утилиты (напр. ruff, black).
uv python dir
Показывает, куда uv скачивает интерпретаторы Python. Если вы использовали uv python install 3.12, исполняемые файлы будут лежать именно здесь, а не в системных папках типа /usr/bin/.
Если вам нужно использовать установленный через uv Python в другом приложении (например, указать путь в PyCharm или VS Code), используйте uv python dir, чтобы найти корень всех установленных версий.
Docker
Использование UV в docker может сильно варьироваться от случая к случаю.
Поэтому рекомендую смотреть здесь шаблоны: https://github.com/astral-sh/uv-docker-example/
Здесь я лишь приведу пример как использую я, соблюдая Docker Best Practices и заботясь о безопасности.
# Stage 1 - Сборка зависимостей
FROM ghcr.io/astral-sh/uv:python3.14-bookworm-slim AS builder
ENV UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy \
UV_NO_DEV=1 \
UV_PYTHON_DOWNLOADS=0 \
UV_LOCKED=1
WORKDIR /app
# 1. Сначала только зависимости (кэшируемый слой)
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --no-install-project --no-dev
# 2. Копируем исходники и устанавливаем сам проект
COPY . /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --no-dev
# Stage 2 - Финальный образ
FROM python:3.14-slim-bookworm
RUN apt-get update && apt-get install -y --no-install-recommends \
postgresql-client && \
rm -rf /var/lib/apt/lists/* && \
groupadd --system --gid 999 appgroup && \
useradd --system --gid 999 --uid 999 --create-home appuser
WORKDIR /app
COPY --from=builder --chown=appuser:appgroup /app /app
ENV PATH="/app/.venv/bin:$PATH" \
PYTHONUNBUFFERED=1
USER appuser
RUN chmod +x ./scripts/entrypoint.sh
ENTRYPOINT ["./scripts/entrypoint.sh"]Stage 1: Сборка (Builder)
На этом этапе мы готовим виртуальное окружение. Мы используем официальный образ astral-sh/uv, в котором uv уже предустановлен.
Переменные окружения (ENV)
UV_COMPILE_BYTECODE=1: Заставляетuvкомпилировать.pyфайлы в.pyc(байт-код) сразу при установке. Это немного увеличивает время сборки, но ускоряет первый запуск приложения в контейнере.UV_LINK_MODE=copy: По умолчаниюuvиспользует хардлинки для скорости. Внутри Docker (особенно при использовании кэш-маунтов) это может вызвать проблемы с правами доступа. Режимcopy— самый надежный для Docker.UV_NO_DEV=1: Гарантирует, что мы не потащим в продакшн-образpytest,ruffи другие тяжелые инструменты разработки.UV_PYTHON_DOWNLOADS=0: Мы используем образ, где Python уже есть (3.14). Этот флаг запрещаетuvпытаться скачать другой Python из сети.UV_LOCKED=1: Проверка: еслиuv.lockне соответствуетpyproject.toml, сборка упадет. Это защита от «разъезда» версий.
Кэширование (RUN —mount)
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --no-install-project --no-dev--mount=type=cacheСамая важная часть. Docker сохраняет папку кэшаuvмежду сборками. Если ты добавишь одну библиотеку,uvне будет качать все остальные заново — он возьмет их из этого кэша.--mount=type=bindМы не копируемuv.lockв образ командойCOPY. Мы «пробрасываем» его только на время выполнения команды. Это делает слой Docker чище.--no-install-projectМы говорим: «Установи только библиотеки из лок-файла, но не пытайся установить текущую папку как пакет». Это позволяет закэшировать тяжелую установку зависимостей отдельно от часто меняющегося кода.
Stage 2: Финальный образ (Runtime)
Здесь мы переходим на чистый образ Python. Нам больше не нужен uv, нам нужно только готовое окружение.
Чистка и безопасность
python:3.14-slim-bookworm: Мы берем минимальный образ (slim), чтобы итоговый вес был меньше (около 100-150 МБ вместо 1 ГБ).- Создание пользователя: Мы создаем
appgroupиappuser. В Docker никогда нельзя запускать код от root. Если злоумышленник взломает приложение, у него не будет прав администратора на сервере.
Перенос результата
COPY --from=builder --chown=appuser:appgroup /app /appМы берем всю папку /app (включая уже созданное виртуальное окружение .venv) из первого этапа и копируем ее сюда. Флаг --chown сразу отдает права на эти файлы нашему безопасному пользователю.
Активация окружения
ENV PATH="/app/.venv/bin:$PATH"Вместо того чтобы писать source .venv/bin/activate (что сложно сделать в Docker), мы просто добавляем путь к бинарникам виртуального окружения в начало системной переменной PATH. Теперь любая команда python или gunicorn будет автоматически браться из нашего окружения.
Итоговая схема работы
| Шаг | Действие | Результат |
|---|---|---|
| 1 | Монтируем кэш и лок-файлы | Быстрый доступ к пакетам без лишних слоев COPY. |
| 2 | uv sync --no-install-project | Создается .venv со всеми либами. Этот слой не меняется, пока ты не обновишь зависимости. |
| 3 | COPY . /app + uv sync | Добавляется твой код. uv мгновенно доставляет сам проект в окружение. |
| 4 | COPY --from=builder | Мы выбрасываем uv, временные файлы и кэш. Оставляем только Python, код и .venv. |
- Время сборки с 0, при медленном интернете:
52.7s - Время повторной сборки при изменении кода:
1.1s - Время повторной сборки при изменении зависимостей:
6s