Python в контейнере: размер образа, слои и безопасность
Образ на python:3.12 весит под гигабайт, собирается медленно и запускается от root. Разбираем, из чего складывается размер, как работают слои и что чинить в безопасности.

Базовый образ python:3.12 тянет за собой около 1 ГБ: полный Debian, компиляторы, заголовки, документация. Вы кладёте туда своё приложение на 200 строк — и получаете артефакт, который выкачивается в CI по три минуты, занимает место в реестре и запускается процессом от root. Дальше — почему так и что с каждым из трёх пунктов можно сделать: размер, слои, безопасность. Ориентируемся на Docker 24+ и Python 3.12, но принципы работают и для podman.
Размер: откуда берётся гигабайт
Официальные теги Python на Docker Hub отличаются на порядок. Разница — в базовом дистрибутиве и в том, что в нём предустановлено.
| Тег | База | Примерный размер | Что внутри |
|---|---|---|---|
python:3.12 | Debian (полный) | ~1 ГБ | gcc, make, dev-заголовки, куча системных пакетов |
python:3.12-slim | Debian slim | ~150 МБ | минимальный Debian, без компиляторов |
python:3.12-alpine | Alpine (musl) | ~50 МБ | musl libc вместо glibc |
Цифры плавают от версии к версии — сверяйте актуальные на Docker Hub. Соблазн взять alpine ради 50 МБ понятен, но есть подвох: Alpine использует musl libc вместо glibc. Пакеты с бинарными колёсами (wheel) под manylinux собраны под glibc и на Alpine не ставятся готовыми — pip начинает собирать их из исходников, а для этого тянет компилятор. В итоге numpy, pandas, psycopg собираются минутами, а образ раздувается временными зависимостями. Для большинства проектов slim — разумная база: маленький, glibc, wheel ставятся бинарными.
Alpine экономит мегабайты образа и отнимает минуты сборки. Для веб-приложения с зависимостями на C выигрыш в размере часто съедается временем CI и болью при отладке segfault в musl.
Слои: почему кеш не срабатывает
Каждая инструкция RUN, COPY и ADD создаёт слой. Docker кеширует слой и переиспользует его, пока не изменилась инструкция или её входные данные. Типичная ошибка — копировать весь проект до установки зависимостей:
# Плохо: любое изменение кода сбрасывает кеш pip
COPY . .
RUN pip install -r requirements.txt
При такой последовательности любая правка в исходниках инвалидирует слой COPY . ., а значит и следующий за ним RUN pip install. Каждый билд заново качает все пакеты. Порядок надо перевернуть: сначала копируем только файл зависимостей, ставим, потом код.
FROM python:3.12-slim
WORKDIR /app
# Сначала только манифест зависимостей
COPY requirements.txt .
# Слой с pip install кешируется, пока requirements.txt не изменился
RUN pip install --no-cache-dir -r requirements.txt
# Код копируем последним — он меняется чаще всего
COPY . .
CMD ["python", "main.py"]
Флаг --no-cache-dir у pip не даёт складывать скачанные пакеты в кеш внутри образа — этот кеш в рантайме бесполезен и только занимает место. Ещё одна ловушка: удаление файлов в отдельном слое не уменьшает образ. Если вы в одном RUN установили пакет, а в следующем RUN удалили — данные остаются в нижнем слое, итоговый размер не падает. Чистить надо в той же инструкции, где создали.
Multi-stage: сборка отдельно, рантайм отдельно
Компилятор и dev-заголовки нужны только на этапе установки зависимостей. В финальный образ их тащить незачем. Multi-stage сборка разделяет эти стадии: в первой ставим и собираем, во вторую копируем только результат.
# --- стадия сборки ---
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
# Ставим зависимости в отдельную директорию
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# --- финальная стадия ---
FROM python:3.12-slim
WORKDIR /app
# Забираем только установленные пакеты, без компиляторов и кеша
COPY --from=builder /install /usr/local
COPY . .
CMD ["python", "main.py"]
Если для сборки колёс нужен gcc, ставьте его через apt-get только в стадии builder — в финальный образ он не попадёт. Проверить результат: docker images покажет размер, а docker history <image> — вклад каждого слоя, чтобы найти, что весит больше всего.
Безопасность: три вещи, которые чинят чаще всего
По умолчанию контейнер запускает процесс от root. Если приложение скомпрометируют, атакующий получает root внутри контейнера — а при неудачной конфигурации рантайма и путь к хосту. Заведите непривилегированного пользователя.
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Создаём пользователя и передаём ему владение рабочей директорией
RUN useradd --create-home --uid 1000 appuser \
&& chown -R appuser:appuser /app
USER appuser
CMD ["python", "main.py"]
- Не root. Инструкция
USER appuserпереключает процесс на непривилегированного пользователя. Учтите: порядок важен — всё, что требует root (установка пакетов), делается доUSER, после него прав уже нет. - Секреты не в образ. Никаких токенов, паролей и ключей в
ENVили в файлах внутри образа. Слои образа читаются кем угодно, у кого есть доступ к реестру. Пробрасывайте секреты через переменные окружения рантайма или черезdocker build --secret— тогда они не оседают в слоях. - .dockerignore. Без него
COPY . .утащит в образ.git,.env, локальные виртуальные окружения и кеши. Заведите.dockerignoreпо образцу.gitignore— это и безопасность, и размер, и скорость сборки.
Отдельно про CVE в базовом образе. Даже slim со временем накапливает уязвимости в системных пакетах. Прогоняйте образ через сканер — trivy image <image> или docker scout — и обновляйте базу регулярно. Прикреплённый тег вроде python:3.12-slim обновляется мейнтейнерами, но только если вы пересобираете образ.
Что сделать дальше
Соберите образ по multi-stage примеру и сравните docker images до и после — на типовом веб-приложении переход с python:3.12 на slim + multi-stage срезает размер в несколько раз. Дальше: настройте сканер уязвимостей в CI, чтобы билд падал на критичных CVE; добавьте HEALTHCHECK, если контейнер живёт под оркестратором; посмотрите в сторону distroless-образов от Google, если хотите убрать из рантайма даже shell и пакетный менеджер. Инвалидация кеша слоёв в CI между раннерами — отдельная тема, здесь мы разобрали только порядок инструкций внутри Dockerfile.
Инфографика для маркетплейса: Три главных преимущества товара короткими фразами, выноски в безопасной зоне превью, точный размер площадки.
Спросить за 60 ₽Источники: Docker — Dockerfile best practices, Docker Official Images — Python
Частые вопросы
У меня не Docker, а podman — примеры подойдут?
Стоит ли брать alpine ради минимального размера?
Почему образ не уменьшился, хотя я удалил файлы в Dockerfile?
Как передать секрет, чтобы он не попал в слои?
Приложение падает с Permission denied после добавления USER appuser
Нужно ли пересобирать образ, если тег python:3.12-slim обновился?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.