Docker для Python в 2026 году: современное руководство по многоэтапной сборке uv +
Откройте любой учебник «Докеризация вашего приложения Python» пятилетней давности, и вы найдете те же четыре строки: FROM python:3.11-slim, COPY require.txt ., RUN pip install -r require.txt, COPY . .. Оно работает. Он также отправляет образ размером более 200 МБ, перестраивает медленно, поскольку каждая установка зависимостей представляет собой полный кэш, пропускающий момент изменения одной строки кода приложения, и незаметно перетаскивает компилятор C и файлы заголовков, к которым ваш работающий контейнер больше никогда не будет обращаться после завершения сборки.
Ничего из этого больше не нужно. Между uv (менеджером пакетов и установщиком на основе Rust от Astral), монтированием кэша BuildKit и базовыми образами без дистрибутива, по умолчанию для производственного контейнера Python 2026 года используется многоэтапная сборка, которая включает только интерпретатор Python, вашу виртуальную среду и ваш код, и ничего больше. В этом руководстве рассматривается старый шаблон, новый шаблон и файл Dockerfile, который вы можете скопировать в реальный проект уже сегодня.
Старый шаблон и почему он дорогой
DOCKERFILEFROM python:3.13-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD ["python", "app.py"]
Эта модель имеет три издержки, которые увеличиваются по мере роста проекта. Во-первых, python:3.13-slim уже поставляется в сжатом виде примерно от 150 до 200 МБ, прежде чем вы добавите одну зависимость, поскольку "slim" по-прежнему содержит полную пользовательскую среду Debian, apt и расширения C стандартной библиотеки Python. Во-вторых, каждый уровень «RUN pip install» кэшируется с помощью хэша содержимого Docker предыдущих слоев, поэтому в файле «app.py» меняется одна строка перед «COPY». . ничего не делает недействительным исходный код, но изменение в requirements.txt` делает недействительным весь этап установки и повторно загружает каждое колесо. В-третьих, если какая-либо зависимость нуждается в собственной сборке (отсутствует колесо ManyLinux, необычная версия пакета), pip незаметно подключает инструменты сборки, которые затем навсегда остаются в окончательном образе, потому что есть только один этап, на котором их можно выбросить.
Новый шаблон: этап построения УФ плюс финальный этап без дистрибутива
Исправление — это многоэтапная сборка: на одном этапе есть компилятор, uv и ваш файл блокировки, а на втором, гораздо меньшем этапе, копируется только готовая виртуальная среда и ваше дерево исходного кода. На последнем этапе никогда не используются pip, uv, apt или компилятор C.
DOCKERFILEFROM python:3.13-slim AS builder COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy UV_PYTHON_DOWNLOADS=never WORKDIR /app 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 --frozen --no-install-project --no-dev COPY . /app RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev FROM gcr.io/distroless/python3-debian12 COPY --from=builder /app /app WORKDIR /app ENV PATH="/app/.venv/bin:$PATH" ENTRYPOINT ["/app/.venv/bin/python", "-m", "app"]
В gcr.io/distroless/python3-debian12 нет ни оболочки, ни менеджера пакетов, ни pip, а это именно то, что вам нужно в рабочей среде: в контейнере не осталось ничего, что злоумышленник, получивший выполнение кода, мог бы использовать для получения дополнительных инструментов. Окончательный образ — это пакеты сайта вашей виртуальной среды плюс ваш код, который для типичного сервиса FastAPI занимает менее 50 МБ вместо более 200 МБ.
Монтируется кеш BuildKit: никогда не загружайте одно и то же колесо дважды
Строка --mount=type=cache,target=/root/.cache/uv выполняет здесь настоящую работу. Без него каждая «сборка докера» на новом раннере повторно загружает каждое колесо из PyPI, потому что кеш слоев сохраняется только в том случае, если предыдущие уровни, включая «COPY uv.lock», являются байтовыми. Монтирование кэша BuildKit — это постоянный том вне графа слоев: он сохраняется при сборках на одном и том же хосте или, с использованием бэкэнда кэша реестра, при выполнении CI, даже при изменении файла блокировки. Соедините его с uv sync --frozen, который отказывается трогать файл блокировки и громко завершает работу, если pyproject.toml и uv.lock не синхронизированы, вместо того, чтобы автоматически разрешать дерево зависимостей, отличное от того, с которым выполнялись ваши тесты.
Отделение шага uv sync --no-install-project от последнего шага uv sync — это вторая половина дела: первый вызов устанавливает только сторонние зависимости (кэшируются до тех пор, пока uv.lock не изменяется), а второй вызов запускается после COPY . /app устанавливает ваш собственный проект без повторного обращения к сторонним пакетам. Измените одну строку кода приложения, и уровень зависимостей останется в кеше.
Собственные зависимости: numpy, Pillow и проблема ManyLinux
Такие пакеты, как numpy и Pillow, поставляют предварительно встроенные колеса Linux для распространенных платформ, поэтому в linux/amd64 или linux/arm64 uv sync обычно просто загружает колесо и никогда не затрагивает компилятор. Режим сбоя проявляется, когда вы используете необычную архитектуру, прикрепляете очень новую версию до того, как колеса догонят вас, или зависите от пакета, который поставляется только с исходным кодом. В этом случае на этапе сборки потребуются инструменты сборки, которые никогда не должны появляться на последнем этапе:
DOCKERFILEFROM python:3.13-slim AS builder RUN apt-get update && apt-get install -y --no-install-recommends build-essential \ && rm -rf /var/lib/apt/lists/* # uv + sync steps as in the stage above
build-essential (заголовки gcc, make, libc) существует только на этапе сборки. На последнем этапе без дистрибутива копируется уже скомпилированный .venv, а не компилятор, который его создал. Если вы хотите попрактиковаться в создании и доставке полноценного контейнерного сервиса, курс FastAPI описывает именно этот тип API, от первой конечной точки до развертывания в рабочей среде.
Python с УФ-управлением: перестаньте зависеть от интерпретатора, поставляемого в базовом образе
python:3.13-slim дает вам любой патч, который Debian решил выпустить на этой неделе. Вместо этого uv может управлять самим интерпретатором: uv python install 3.13.1 загружает конкретную, воспроизводимую сборку CPython из автономного проекта python-build-build и закрепляет ее в .python-version, так что ваша сборочная стадия и ваш ноутбук запускают один и тот же интерпретатор вплоть до версии исправления. В сочетании с UV_PYTHON_DOWNLOADS=never в файле Dockerfile, поэтому неправильно закрепленная версия громко завершает сборку вместо того, чтобы молча загружать что-то другое, это закрывает пробел «работает на моей машине, не работает на бегуне», который плавающий тег базового изображения оставляет открытым.
Отладка контейнера без оболочки
Единственная реальная цена дистрибутива: там нет оболочки, поэтому docker exec -it контейнер sh не работает. Для локальной отладки создайте тот же файл Dockerfile с целью, которая останавливается на этапе сборки (docker build --target=builder -t myapp:debug .), который по-прежнему содержит полное пользовательское пространство python:3.13-slim и оболочку. Держите эту цель отладки вне вашего конвейера развертывания; он существует для вашего терминала, а не для производства.
Однофайловый шаблон
DOCKERFILEFROM python:3.13-slim AS builder COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy UV_PYTHON_DOWNLOADS=never WORKDIR /app 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 --frozen --no-install-project --no-dev COPY . /app RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev FROM gcr.io/distroless/python3-debian12 COPY --from=builder /app /app WORKDIR /app ENV PATH="/app/.venv/bin:$PATH" ENTRYPOINT ["/app/.venv/bin/python", "-m", "app"]
Скопируйте это, измените имя модуля ENTRYPOINT, чтобы оно соответствовало макету вашего проекта, и вы получите сборку, которая меньше по размеру, быстрее при повторных сборках и ее труднее атаковать, чем шаблон pip install -r require.txt, которому все еще учат в большинстве руководств. Готовы попрактиковаться в бэкэнде, который находится внутри этого контейнера? Начните с FastAPI.
Часто задаваемые вопросы
Нужно ли включать BuildKit для работы монтирования кэша?
Да, --mount=type=cache является функцией только BuildKit. Он был сборщиком по умолчанию в Docker Desktop и последних выпусках Docker Engine, но в старых версиях CI вам может потребоваться явно указать DOCKER_BUILDKIT=1 или docker buildx build вместо простой docker build.
Почему бы просто не использовать python:3.13-alpine для изображения меньшего размера?
musl libc от Alpine нарушает предположение о многолинуксовом колесе, на которое опираются numpy, Pillow и большинство скомпилированных пакетов, поэтому вы теряете готовые колеса и либо компилируете из исходного кода, либо сталкиваетесь с тонкими ошибками во время выполнения. distroless-glibc позволяет избежать этой ловушки.
Ув полностью заменяет pip и Poetry?
Для большинства новых проектов — да. uv разрешает и устанавливает быстрее, читает тот же pyproject.toml и генерирует файл блокировки, но если команда уже зависит от плагинов Poetry или рабочий процесс uv еще не охватывает, нет необходимости срочно выполнять миграцию в середине проекта. Сначала попробуйте УФ на новом сервисе.
Что делать, если моему базовому образу требуется исправление безопасности после того, как я уже создал образ?
Восстановить. Закрепите дайджест, а не просто тег, для python:3.13-slim на этапе сборки и перестраивайте по расписанию, для большинства команд разумно еженедельно, поэтому исправленный базовый образ переносится в новую сборку, а не остается устаревшим в течение нескольких месяцев.
Могу ли я использовать этот шаблон для проекта Django вместо FastAPI?
Да, Dockerfile не имеет значения, какой фреймворк вы используете; изменяется только последняя точка ENTRYPOINT (например, unicorn myproject.wsgi:application). Курс FastAPI — это быстрый способ отработать шаблон до конца, если вы запускаете новый сервис.
Готова ли к выпуску версия без дистрибутива или она все еще находится в стадии эксперимента?
Широко используется в производстве; Google поддерживает его, и многие команды поставляют его в качестве базы Python по умолчанию. Платой за вход является отсутствие оболочки, что является сознательным выбором безопасности, а не ошибкой.