Перейти до основного вмісту
Інструмент Python2026-11-01 · 11 хв читання

Docker для Python у 2026 році: сучасний uv + багатоетапний посібник із збирання

Відкрийте будь-який навчальний посібник «Dockerize your Python app» п’ятирічної давності, і ви знайдете ті самі чотири рядки: FROM python:3.11-slim, COPY requirements.txt ., RUN pip install -r requirements.txt, COPY . .. Це працює. Він також надсилає образ розміром понад 200 МБ, повільно перебудовується, оскільки кожне встановлення залежностей — це повний кеш, який пропускає момент зміни одного рядка коду програми, і тихо перетягує компілятор C і файли заголовків, яких ваш запущений контейнер більше ніколи не торкатиметься після завершення збирання.

Нічого з цього більше не потрібно. Серед uv (менеджера пакунків і інсталятора на базі Rust від Astral), кріплень кешу BuildKit і базових образів без дистрибутивів, стандартний варіант 2026 для робочого контейнера Python — це багатоетапна збірка, яка надсилає лише інтерпретатор Python, ваше віртуальне середовище та ваш код, нічого більше. У цьому посібнику описано старий шаблон, новий шаблон і файл Docker, який ви можете скопіювати в реальний проект вже сьогодні.

Старий шаблон і чому він дорогий

DOCKERFILE
FROM 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.

DOCKERFILE
FROM 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, містять попередньо зібрані колеса manylinux для звичайних платформ, тому на linux/amd64 або linux/arm64 uv sync зазвичай просто завантажує колесо й ніколи не торкається компілятора. Режим помилки з’являється, коли ви використовуєте незвичайну архітектуру, закріплюєте дуже новий випуск, перш ніж колеса наздоженуть, або залежите від пакета, який надсилає лише вихідний дистрибутив. У цьому випадку на стадії розробника потрібні інструменти для побудови, на фінальній стадії ніколи не повинно бути:

DOCKERFILE
FROM 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, від першої кінцевої точки до розгортання у формі виробництва.

Uv-managed Python: припинити залежність від того, який інтерпретатор надіслано базовим зображенням

python:3.13-slim дає вам будь-який випуск виправлення, який Debian вирішив надіслати того тижня. Натомість uv може керувати самим інтерпретатором: uv python install 3.13.1 завантажує конкретну, відтворювану збірку CPython із автономного проекту python-build-standalone і закріплює її у .python-version, тож на етапі розробника та на вашому ноутбуці запускається той самий інтерпретатор до версії виправлення. У поєднанні з UV_PYTHON_DOWNLOADS=never у Dockerfile, тому неправильно закріплена версія голосно закриває збірку, а не тихо завантажує щось інше, це закриває прогалину "працює на моїй машині, не працює на бігуні", яку залишає відкритим тег плаваючого базового зображення.

Налагодження контейнера без оболонки

Єдина реальна вартість distroless: немає оболонки, тому docker exec -it container sh не працює. Для локального налагодження створіть той самий Dockerfile із метою, яка зупиняється на етапі побудови (docker build --target=builder -t myapp:debug .), який все ще містить повну область користувача python:3.13-slim і оболонку. Тримайте ціль налагодження поза конвеєром розгортання; він існує для вашого терміналу, а не для виробництва.

Однофайловий шаблон

DOCKERFILE
FROM 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.

FAQ

Чи потрібно ввімкнути 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 і генерує файл блокування, але якщо команда вже залежить від плагінів Poetry або робочий процес, який uv ще не охоплює, немає термінової міграції в середині проекту. Спочатку спробуйте uv на новій службі.

Що робити, якщо мій базовий образ потребує виправлення безпеки після того, як я вже створив образ?

Перебудувати. Закріпіть дайджест, а не просто тег, для python:3.13-slim на стадії конструктора та відновлюйте за розкладом, щотижня є розумним для більшості команд, тому виправлений базовий образ перетікає в нову збірку, а не простоює місяцями.

Чи можу я використовувати цей шаблон для проекту Django замість FastAPI?

Так, Dockerfile не має значення, який фреймворк ви запускаєте; змінюється лише кінцева ENTRYPOINT (наприклад, gunicorn myproject.wsgi:application). Курс FastAPI — це швидкий спосіб відпрацювати шаблон від кінця до кінця, якщо ви починаєте нову службу.

Чи готовий до виробництва без дистрибутива, чи він все ще експериментальний?

Широко використовується у виробництві; Google підтримує його, і багато команд доставляють його як стандартну базу Python. Компромісом, який потрібно прийняти, є відсутність оболонки, що є навмисним вибором безпеки, а не помилкою.

Готові спробувати?

15 безкоштовних уроків після безкоштовної реєстрації — без картки. Спробуйте платформу та вирішуйте.

Почніть безкоштовно →

Or unlock every lesson

Pro — $12/mo, unlimited AI, cancel any time. Refund within 14 days worldwide.

See Pro plans →

Get one Python lesson + one career idea every Friday

No spam, no "buy our course now". Three bullets, every Friday. Unsubscribe with one click.