Docker за Python през 2026 г.: Модерното ръководство за uv + многоетапно изграждане
Отворете който и да е петгодишен урок „Dockerize your Python app“ и ще намерите същите четири реда: FROM python:3.11-slim, COPY requirements.txt ., RUN pip install -r requirements.txt, COPY . .. Работи. Той също така изпраща 200MB-плюс изображение, възстановява се бавно, тъй като всяка инсталация на зависимост е пълен кеш, пропускащ момента, в който един ред от кода на приложението се промени, и тихо влачи C компилатор и заглавни файлове, които вашият работещ контейнер никога няма да докосне отново, след като изграждането приключи.
Нищо от това вече не е необходимо. Между uv (базиран на Rust пакетен мениджър и инсталатор от Astral), монтирания на кеша на BuildKit и базови изображения без дистрибуция, 2026 по подразбиране за производствен контейнер на Python е многоетапна компилация, която доставя само интерпретатор на 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 до 200MB компресиран, преди да добавите една зависимост, тъй като "slim" все още носи пълна потребителска област на Debian, apt и C разширенията на стандартната библиотека на Python. Второ, всеки слой RUN pip install се кешира от хеша на съдържанието на Docker на предходните слоеве, така че се променя един ред в app.py преди COPY. . не прави нищо нагоре по веригата, но промяна в requirements.txt` прави невалидна цялата стъпка на инсталиране и изтегля отново всяко колело. Трето, ако някоя зависимост се нуждае от собствена компилация (липсващо колело на manylinux, необичайна версия на пакет), pip тихо изтегля инструменти за компилация, които след това остават в окончателното изображение завинаги, защото има само един етап, в който да го изхвърлите.
Новият модел: UV строителен етап плюс краен етап без разпределение
Корекцията е многоетапно изграждане: един етап, който има компилатор, 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 услуга е доста под 50MB вместо над 200MB.
Монтиране на кеша на BuildKit: никога не изтегляйте повторно едно и също колело два пъти
Редът --mount=type=cache,target=/root/.cache/uv върши истинската работа тук. Без него всяка докер компилация на нов runner изтегля отново всяко колело от PyPI, тъй като кешът на слоевете оцелява само ако предходните слоеве, включително COPY uv.lock, са байтове идентични. Монтирането на кеша на BuildKit е постоянен том извън графиката на слоя: той оцелява между компилации на един и същ хост или, с бекенда на кеша на регистъра, в CI runners, дори когато заключващият файл се промени. Сдвоете го с uv sync --frozen, който отказва да докосне заключващия файл и се проваля шумно, ако pyproject.toml и uv.lock не са синхронизирани, вместо тихо да разрешава различно дърво на зависимости от това, срещу което се изпълняват вашите тестове.
Разделянето на стъпката uv sync --no-install-project от крайния uv sync е другата половина на трика: първото извикване инсталира само зависимости от трети страни (кеширани, докато uv.lock не се промени), а второто извикване, стартирайте след COPY. /app, инсталира вашия собствен проект, без да докосвате повторно пакети на трети страни. Променете един ред от кода на приложението и слоят на зависимостта остава кеширан.
Родни зависимости: numpy, pillow и проблемът с manylinux
Пакети като numpy и Pillow доставят предварително компилирани колела на manylinux за общите платформи, така че на 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 headers) съществува само в етапа на компилиране. Крайният етап без дистрибуция копира вече компилирания .venv, никога компилатора, който го е създал. Ако искате да практикувате изграждането и доставката на истинска контейнеризирана услуга от край до край, курсът по FastAPI преминава през точно този вид API, от първата крайна точка до внедряване във форма на производство.
uv-управляван Python: спиране в зависимост от интерпретатора, изпратен от базовото изображение
python:3.13-slim ви дава каквато и да е версия на корекцията, която Debian реши да изпрати тази седмица. uv може да управлява самия интерпретатор вместо това: uv python install 3.13.1 изтегля конкретна, възпроизводима CPython компилация от самостоятелния проект python-build-standalone и я закрепва в .python-version, така че вашият етап на компилиране и вашият лаптоп изпълняват абсолютно същия интерпретатор до версията на корекцията. В комбинация с UV_PYTHON_DOWNLOADS=never в Dockerfile, така че грешно закачената версия проваля компилацията шумно, вместо тихо да изтегля нещо различно, това затваря празнината „работи на моята машина, не успява на runner“, която плаващият таг на основното изображение оставя отворен.
Отстраняване на грешки в контейнер без обвивка
Единствената реална цена на distroless: няма обвивка, така че docker exec -it container 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 requirements.txt, който повечето уроци все още преподават. Готови ли сте да практикувате бекенда, който влиза в този контейнер? Започнете с FastAPI.
ЧЗВ
Имам ли нужда от активиран BuildKit, за да работи монтирането на кеша?
Да, --mount=type=cache е функция само за BuildKit. Това е компилаторът по подразбиране в Docker Desktop и последните версии на Docker Engine, но при по-стари CI програми може да се наложи да зададете изрично DOCKER_BUILDKIT=1 или docker buildx build вместо обикновено docker build.
Защо просто не използвате python:3.13-alpine за по-малко изображение?
Мусл libc на Alpine нарушава предположението за колелото на manylinux, на което разчитат numpy, Pillow и повечето компилирани пакети, така че губите предварително компилирани колела и или компилирате от източника, или попадате на фини грешки по време на изпълнение. distroless-glibc избягва този капан.
UV замества ли напълно pip и Poetry?
За повечето нови проекти, да. uv разрешава и инсталира по-бързо, чете същия pyproject.toml и генерира заключващ файл, но ако даден екип вече зависи от плъгини за поезия или работен процес, който uv все още не покрива, няма спешност за мигриране по средата на проекта. Първо опитайте uv на нова услуга.
Какво ще стане, ако основното ми изображение се нуждае от корекция за сигурност, след като вече съм създал изображението?
Възстановете. Закачете дайджест, а не само таг, за python:3.13-slim в етапа на компилиране и възстановявайте по график, седмично е разумно за повечето екипи, така че закърпеното основно изображение се влива в ново компилиране, вместо да стои остаряло с месеци.
Мога ли да използвам този модел за Django проект вместо FastAPI?
Да, Dockerfile не се интересува коя рамка изпълнявате; променя се само крайната ENTRYPOINT (например gunicorn myproject.wsgi:application). Курсът по FastAPI е бърз начин да практикувате шаблона от край до край, ако стартирате нова услуга.
Готово ли е за производство без дистрибуция или все още е експериментално?
Намира широко приложение в производството; Google го поддържа и много екипи го доставят като своя Python база по подразбиране. Компромисът, който трябва да приемете, е липсата на обвивка, което е умишлен избор за сигурност, а не грешка.