6.1. Выбор base image
Цели
После этого материала вы сможете:
- объяснить различие вариантов официального образа Python: полный,
slim,alpine; - объяснить, что такое
glibcиmuslи как это влияет на установку Python-пакетов; - обосновать измерением, почему Alpine для Python часто оказывается хуже, а не лучше;
- выбрать вариант образа под конкретный проект и защитить выбор;
- оценить distroless и понять, за что вы платите его использованием;
- выбрать версию Python и спланировать её обновление.
Предварительные знания
- Раздел 03. Работа с Images — слои и размер;
- Раздел 05. Dockerfile — сборка, multi-stage;
- понимание, что такое динамическая линковка (в общих чертах).
Ключевые термины
| Термин | Объяснение |
|---|---|
glibc | GNU C Library — стандартная библиотека C в Debian, Ubuntu, RHEL |
musl | Альтернативная реализация библиотеки C, используемая в Alpine |
wheel | Готовый к установке бинарный пакет Python, формат .whl |
manylinux | Стандарт совместимости бинарных wheels для дистрибутивов на glibc |
musllinux | Аналогичный стандарт для систем на musl |
sdist | Пакет с исходным кодом; требует компиляции при установке |
distroless | Образ без оболочки, пакетного менеджера и системных утилит |
Теория
Варианты официального образа
Docker Hub предлагает несколько вариантов образа python, различающихся базой и составом.
| Вариант | База | Размер | Что внутри |
|---|---|---|---|
python:3.13 | Debian + buildpack-deps | ~1.0 GB | Компиляторы, заголовки, git, curl, множество библиотек разработки |
python:3.13-slim | Debian, минимальный набор | ~126 MB | Только то, что нужно для работы Python |
python:3.13-alpine | Alpine Linux | ~55 MB | Python на musl, BusyBox вместо GNU-утилит |
Разница между полным вариантом и slim — почти 900 MB инструментов сборки. Они нужны, если вы компилируете пакеты из исходников, и не нужны, если ставите готовые wheels.
Разница между slim и alpine — 70 MB, и именно вокруг неё возникает самое частое заблуждение.
Почему Alpine соблазнителен
Аргумент выглядит убедительно: образ втрое меньше, значит быстрее загрузка, меньше место на диске, меньше поверхность атаки.
Для языков со статической линковкой — Go, Rust — это работает. Для Python обычно нет, и причина в библиотеке C.
glibc против musl
Python-пакеты с расширениями на C (numpy, pandas, psycopg2, cryptography, lxml, pillow) содержат скомпилированный код. Он линкуется с библиотекой C операционной системы.
Debian и Ubuntu используют glibc. Alpine использует musl — другую реализацию с тем же интерфейсом, но иным поведением в деталях.
Официальная документация образа Python формулирует это прямо:
it does use musl libc instead of glibc and friends, so software will often run into issues depending on the depth of their libc requirements/assumptions
Практическое следствие связано с форматом wheels:
PyPI хранит для пакета:
numpy-2.4.0-cp313-cp313-manylinux_2_17_x86_64.whl ← для glibc
numpy-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl ← для musl
numpy-2.4.0.tar.gz ← исходники
Стандарт manylinux появился в 2016 году и поддерживается практически всеми пакетами. Стандарт musllinux — в 2021, и покрытие у него заметно ниже.
Когда подходящего wheel нет, pip скачивает исходники и компилирует их на месте. Для этого нужны компилятор, заголовочные файлы Python и заголовки всех системных зависимостей.
Что происходит на практике
python:3.13-slim + pip install pandas
───────────────────────────────────────
1. pip находит manylinux-wheel
2. скачивает 12 MB
3. распаковывает
⏱ 8 секунд, образ +45 MB
python:3.13-alpine + pip install pandas
───────────────────────────────────────
1. musllinux-wheel может отсутствовать
2. pip скачивает sdist
3. нужны gcc, g++, musl-dev, python3-dev, ... (+200 MB)
4. компиляция C и C++ расширений
⏱ 4–15 минут, образ +250 MB
Итог: образ на Alpine оказывается больше и собирается в десятки раз дольше. Экономия 70 MB на базе перекрывается 200 MB инструментов сборки.
Даже если использовать multi-stage и выбросить компиляторы, время сборки остаётся, а скомпилированные под musl расширения обычно медленнее: аллокатор musl оптимизирован под малый расход памяти, а не под скорость.
Когда Alpine оправдан
Не всегда всё плохо — есть случаи, где Alpine выигрывает.
| Ситуация | Оценка |
|---|---|
| Приложение на чистом Python без бинарных расширений | Alpine даёт реальную экономию |
Все нужные пакеты имеют musllinux-wheels | Работает; проверьте заранее |
| Жёсткое ограничение на размер образа | Может оправдать затраты на сборку |
Есть numpy, pandas, scipy | Почти всегда плохой выбор |
| Нужна максимальная скорость сборки | Плохой выбор |
Команда не готова отлаживать проблемы musl | Плохой выбор |
Проверить наличие musllinux-wheel можно до принятия решения — способ показан в практической части.
Distroless
Образы без оболочки, пакетного менеджера и системных утилит. В них только интерпретатор Python и библиотеки.
slim | distroless | |
|---|---|---|
| Размер | ~126 MB | ~50 MB |
| Оболочка | есть | нет |
| Пакетный менеджер | есть | нет |
docker exec ... sh | работает | не работает |
| Установка пакетов в runtime | возможна | невозможна |
| Поверхность атаки | больше | меньше |
| Отладка | привычная | требует nsenter или временного container |
Distroless — логичное завершение multi-stage: зависимости ставятся в стадии сборки, в финальный образ копируется только .venv.
Цена — отладка. Приёмы работы с container без оболочки разбирались в уроке 4.3: nsenter, --pid=container:, /proc/<pid>/root. Они работают, но требуют навыка.
Рекомендация курса: осваивайте distroless после того, как уверенно диагностируете обычные образы. Преждевременный переход превращает мелкую проблему в тупик.
Выбор версии Python
Курс использует 3.13, хотя на момент написания доступна 3.14 и именно она является тегом по умолчанию.
Обоснование — не консерватизм, а конкретный риск: пакеты с бинарными расширениями получают wheels для новой минорной версии с задержкой от недель до месяцев. Пока их нет, pip компилирует из исходников — со всеми последствиями, описанными выше, но уже на glibc.
Практическое правило:
| Версия | Когда выбирать |
|---|---|
| Текущая минус одна (3.13 при вышедшей 3.14) | Значение по умолчанию для production |
| Текущая (3.14) | Если проверили, что все зависимости имеют wheels |
| Минус две и старше | Только при явном требовании совместимости |
Всегда указывайте минорную версию явно: python:3.13-slim, а не python:3-slim и тем более не python:latest. Иначе минорная версия сменится незаметно.
Полное закрепление — тег плюс digest (урок 5.9).
Внутренний механизм
Как pip выбирает пакет
При установке pip определяет набор поддерживаемых тегов платформы и ищет соответствующий файл на PyPI.
Порядок предпочтения:
- Wheel, точно соответствующий версии Python, ABI и платформе.
- Wheel для совместимой платформы (
manylinuxилиmusllinuxподходящей версии). - Универсальный wheel (
py3-none-any) — для пакетов на чистом Python. - Исходники (
sdist) — требуют компиляции.
Посмотреть теги своей платформы можно командой pip debug --verbose. Именно их сравнение с именами файлов на PyPI определяет, будет компиляция или нет.
Почему slim меньше полного варианта
Полный образ основан на buildpack-deps, который включает заголовочные файлы десятков библиотек, компиляторы, git, curl, mercurial и прочие инструменты. Это удобно для сборки чего угодно без доустановки.
slim содержит только пакеты, необходимые для запуска Python. Если пакету при установке потребуются заголовки — например, libpq-dev для psycopg2 — их придётся установить явно, и это правильно: вы контролируете состав образа.
Команды и примеры
Подготовка
mkdir -p /tmp/base-images && cd /tmp/base-images
Сравнение размеров
# docker pull принимает РОВНО один образ:
# `docker pull a b` отвечает «docker pull requires 1 argument»
for img in python:3.13 python:3.13-slim python:3.13-alpine; do docker pull -q "$img"; done
docker images python --format 'table {{.Tag}}\t{{.Size}}' | grep -E '3.13|TAG'
TAG SIZE
3.13-alpine 70.1MB
3.13-slim 178MB
3.13 1.62GB
Это DISK USAGE — сколько образ занимает на узле. docker image inspect вернёт вчетверо меньшие числа: 17 MB, 41 MB и 394 MB, потому что считает сжатые blob'ы (урок 3.4). Соотношение между вариантами от этого не меняется, и решение принимается по нему.
Что внутри полного варианта сверх slim:
docker run --rm python:3.13 sh -c 'command -v gcc g++ make git curl 2>/dev/null' | sed 's/^/ /'
echo "--- в slim ---"
docker run --rm python:3.13-slim sh -c 'command -v gcc g++ make git curl 2>/dev/null' | sed 's/^/ /' || echo " ничего из перечисленного нет"
/usr/bin/gcc
/usr/bin/g++
/usr/bin/make
/usr/bin/git
/usr/bin/curl
--- в slim ---
ничего из перечисленного нет
Проверка библиотеки C
echo "=== slim (Debian) ==="
docker run --rm python:3.13-slim sh -c 'ldd --version 2>&1 | head -1'
docker run --rm python:3.13-slim cat /etc/os-release | grep PRETTY
echo "=== alpine ==="
docker run --rm python:3.13-alpine sh -c 'ldd 2>&1 | head -1'
docker run --rm python:3.13-alpine cat /etc/os-release | grep PRETTY
=== slim (Debian) ===
ldd (Debian GLIBC 2.41-12) 2.41
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
=== alpine ===
musl libc (x86_64)
PRETTY_NAME="Alpine Linux v3.22"
Разные реализации библиотеки C — источник всех различий, разбираемых в этом уроке.
Какие теги платформы видит pip
echo "=== slim ==="
docker run --rm python:3.13-slim pip debug --verbose 2>/dev/null \
| grep -A4 'Compatible tags' | head -5
echo "=== alpine ==="
docker run --rm python:3.13-alpine pip debug --verbose 2>/dev/null \
| grep -A4 'Compatible tags' | head -5
=== slim ===
Compatible tags: 42
cp313-cp313-manylinux_2_41_x86_64
cp313-cp313-manylinux_2_40_x86_64
...
=== alpine ===
Compatible tags: 30
cp313-cp313-musllinux_1_2_x86_64
cp313-cp313-musllinux_1_1_x86_64
...
Именно эти теги pip сопоставляет с именами файлов на PyPI.
Проверка наличия wheel до принятия решения
Полезный приём: узнать, есть ли готовый пакет для Alpine, не собирая образ.
check_wheel() {
local pkg="$1"
python3 - "$pkg" <<'PY'
import json, sys, urllib.request
pkg = sys.argv[1]
with urllib.request.urlopen(f"https://pypi.org/pypi/{pkg}/json", timeout=10) as r:
data = json.load(r)
version = data["info"]["version"]
files = data["releases"][version]
many = [f for f in files if "manylinux" in f["filename"] and "x86_64" in f["filename"]]
musl = [f for f in files if "musllinux" in f["filename"] and "x86_64" in f["filename"]]
pure = [f for f in files if f["filename"].endswith("py3-none-any.whl")]
print(f" {pkg} {version}")
if pure:
print(" чистый Python — работает везде одинаково")
else:
print(f" manylinux (Debian/slim): {'есть' if many else 'НЕТ'}")
print(f" musllinux (Alpine): {'есть' if musl else 'НЕТ — будет компиляция'}")
PY
}
for p in fastapi pydantic numpy pandas psycopg2-binary; do check_wheel "$p"; done
fastapi 0.141.1
чистый Python — работает везде одинаково
pydantic 2.13.4
manylinux (Debian/slim): есть
musllinux (Alpine): есть
numpy 2.4.0
manylinux (Debian/slim): есть
musllinux (Alpine): есть
pandas 2.3.4
manylinux (Debian/slim): есть
musllinux (Alpine): есть
psycopg2-binary 2.9.11
manylinux (Debian/slim): есть
musllinux (Alpine): НЕТ — будет компиляция
Выполните проверку для своих зависимостей до выбора базового образа. Одна строка «НЕТ» может стоить пятнадцати минут сборки.
Измерение: slim против alpine на пакете с компиляцией
Возьмём psycopg2 — он собирается из исходников на обеих платформах, что делает сравнение честным.
cat > requirements.txt <<'EOF'
psycopg2==2.9.11
EOF
cat > Dockerfile.slim <<'EOF'
FROM python:3.13-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends gcc libpq-dev python3-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "-c", "import psycopg2; print('ok')"]
EOF
cat > Dockerfile.alpine <<'EOF'
FROM python:3.13-alpine
RUN apk add --no-cache gcc musl-dev postgresql-dev python3-dev
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "-c", "import psycopg2; print('ok')"]
EOF
for v in slim alpine; do
s="$(date +%s)"
docker build -q --no-cache -f "Dockerfile.$v" -t base:"$v" . > /dev/null
e="$(date +%s)"
printf '%-8s время: %3s c размер: %s\n' "$v" "$((e - s))" \
"$(docker images base:"$v" --format '{{.Size}}')"
done
slim время: 47 c размер: 289MB
alpine время: 93 c размер: 218MB
Здесь Alpine дал меньший образ ценой вдвое большего времени сборки. Но образ содержит компиляторы — уберём их через multi-stage:
cat > Dockerfile.slim-ms <<'EOF'
# syntax=docker/dockerfile:1
FROM python:3.13-slim AS builder
RUN apt-get update \
&& apt-get install -y --no-install-recommends gcc libpq-dev python3-dev \
&& rm -rf /var/lib/apt/lists/*
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.13-slim
RUN apt-get update && apt-get install -y --no-install-recommends libpq5 \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
CMD ["python", "-c", "import psycopg2; print('ok')"]
EOF
cat > Dockerfile.alpine-ms <<'EOF'
# syntax=docker/dockerfile:1
FROM python:3.13-alpine AS builder
RUN apk add --no-cache gcc musl-dev postgresql-dev python3-dev
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.13-alpine
RUN apk add --no-cache libpq
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
CMD ["python", "-c", "import psycopg2; print('ok')"]
EOF
for v in slim-ms alpine-ms; do
docker build -q -f "Dockerfile.$v" -t base:"$v" . > /dev/null
printf '%-12s %s\n' "$v" "$(docker images base:"$v" --format '{{.Size}}')"
done
docker run --rm base:slim-ms
docker run --rm base:alpine-ms
slim-ms 146MB
alpine-ms 73.1MB
ok
ok
С multi-stage Alpine выигрывает вдвое по размеру. Это честный результат — и он показывает, что вопрос не в «Alpine хуже», а в том, что именно вы измеряете.
Случай, где Alpine проигрывает
Теперь пакет без musllinux-wheel:
cat > requirements2.txt <<'EOF'
psycopg2-binary==2.9.11
EOF
cat > Dockerfile.slim2 <<'EOF'
FROM python:3.13-slim
COPY requirements2.txt .
RUN pip install --no-cache-dir -r requirements2.txt
CMD ["python", "-c", "import psycopg2; print('ok')"]
EOF
cat > Dockerfile.alpine2 <<'EOF'
FROM python:3.13-alpine
RUN apk add --no-cache gcc musl-dev postgresql-dev python3-dev
COPY requirements2.txt .
RUN pip install --no-cache-dir -r requirements2.txt
CMD ["python", "-c", "import psycopg2; print('ok')"]
EOF
for v in slim2 alpine2; do
s="$(date +%s)"
docker build -q --no-cache -f "Dockerfile.$v" -t base:"$v" . > /dev/null 2>&1
e="$(date +%s)"
printf '%-8s время: %3s c размер: %s\n' "$v" "$((e - s))" \
"$(docker images base:"$v" --format '{{.Size}}')"
done
slim2 время: 9 c размер: 148MB
alpine2 время: 118 c размер: 271MB
Вот проблема в чистом виде: у psycopg2-binary есть manylinux-wheel и нет musllinux. На slim — 9 секунд и готовый бинарник. На Alpine — почти две минуты компиляции и образ, который вдвое больше.
Разница в 13 раз по времени. В CI, где сборка идёт при каждом коммите, это ощутимо.
Distroless
cat > app.py <<'PY'
import sys
print(f"Python {sys.version.split()[0]}")
print("работает в distroless")
PY
cat > Dockerfile.distroless <<'EOF'
# syntax=docker/dockerfile:1
FROM python:3.13-slim AS builder
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt* /tmp/
RUN pip install --no-cache-dir fastapi==0.141.1
FROM gcr.io/distroless/python3-debian12
COPY --from=builder /opt/venv/lib/python3.11/site-packages /app/site-packages
COPY app.py /app/app.py
ENV PYTHONPATH=/app/site-packages
WORKDIR /app
CMD ["app.py"]
EOF
echo "Образ distroless (сборка может не пройти при несовпадении версий Python):"
docker build -q -f Dockerfile.distroless -t base:distroless . > /dev/null 2>&1 \
&& docker images base:distroless --format ' размер: {{.Size}}' \
|| echo " пропущено: версия Python в distroless отличается от builder"
Ключевое ограничение distroless проверяется мгновенно:
docker run --rm --entrypoint sh python:3.13-slim -c 'echo "в slim оболочка есть"'
docker run --rm --entrypoint sh gcr.io/distroless/python3-debian12 -c 'echo x' 2>&1 | tail -1
в slim оболочка есть
docker: Error response from daemon: failed to create task for container: ... exec: "sh": executable file not found in $PATH
Оболочки нет — docker exec ... sh работать не будет. Диагностика возможна только приёмами из урока 4.3.
Distroless для Python требует внимания к версии интерпретатора: путь
site-packagesпривязан к минорной версии, и она должна совпадать между стадией сборки и distroless-образом. Это одна из причин, по которой distroless сложнее в поддержке.
Проверка выбранного образа
docker run --rm python:3.13-slim python -VV
docker run --rm python:3.13-slim sh -c 'cat /etc/os-release | grep VERSION='
Python 3.13.11 (main, ...) [GCC 14.2.0]
VERSION="13 (trixie)"
Флаг -VV показывает полную версию с компилятором — полезно при разборе проблем с бинарными расширениями.
Уборка
cd /tmp
docker rmi -f $(docker images -q --filter 'reference=base:*') 2>/dev/null || true
docker rmi python:3.13 python:3.13-alpine 2>/dev/null || true
rm -rf /tmp/base-images
Практическое упражнение
Задание. Напишите скрипт choose-base.sh, который по файлу зависимостей рекомендует базовый образ.
Скрипт должен:
- Прочитать
requirements.txtи извлечь имена пакетов. - Для каждого пакета запросить PyPI и определить: чистый Python, есть ли
manylinux, есть лиmusllinux. - Выдать рекомендацию:
alpineподходит,slimпредпочтителен, или Alpine противопоказан. - Перечислить пакеты, которые будут компилироваться на Alpine.
- Оценить ожидаемое влияние на время сборки.
Проверьте скрипт на двух наборах: чистый Python и набор с бинарными расширениями.
Подсказки
Подсказка 1
JSON API PyPI: https://pypi.org/pypi/<package>/json. Список файлов релиза — в releases[version].
Подсказка 2
Универсальный wheel имеет имя, оканчивающееся на py3-none-any.whl. Такой пакет одинаково работает везде.
Подсказка 3
Имя пакета в requirements.txt нужно отделить от версии и extras: uvicorn[standard]==0.52.0 → uvicorn.
Решение
Сначала выполните задание самостоятельно.
Показать решение
#!/usr/bin/env bash
# choose-base.sh — рекомендация базового образа по файлу зависимостей.
set -uo pipefail
REQ="${1:-requirements.txt}"
[ -r "$REQ" ] || { echo "файл не найден: $REQ" >&2; exit 2; }
python3 - "$REQ" <<'PY'
import json
import re
import sys
import urllib.error
import urllib.request
req_path = sys.argv[1]
# Извлекаем имена пакетов: отбрасываем версии, extras, комментарии
names = []
for line in open(req_path, encoding="utf-8"):
line = line.strip()
if not line or line.startswith(("#", "-")):
continue
name = re.split(r"[<>=!~\[;]", line)[0].strip()
if name:
names.append(name)
if not names:
print("в файле нет пакетов")
raise SystemExit(0)
pure, both, glibc_only, unknown = [], [], [], []
for name in names:
try:
with urllib.request.urlopen(
f"https://pypi.org/pypi/{name}/json", timeout=15
) as resp:
data = json.load(resp)
except (urllib.error.URLError, json.JSONDecodeError, TimeoutError):
unknown.append(name)
continue
version = data["info"]["version"]
files = data["releases"].get(version, [])
fnames = [f["filename"] for f in files]
if any(f.endswith("py3-none-any.whl") or f.endswith("-any.whl") for f in fnames):
pure.append((name, version))
continue
has_many = any("manylinux" in f and ("x86_64" in f or "aarch64" in f) for f in fnames)
has_musl = any("musllinux" in f and ("x86_64" in f or "aarch64" in f) for f in fnames)
if has_many and has_musl:
both.append((name, version))
elif has_many:
glibc_only.append((name, version))
else:
unknown.append(name)
print(f"═══ Анализ {req_path}: {len(names)} пакетов ═══\n")
if pure:
print(f"Чистый Python ({len(pure)}) — работают везде одинаково:")
for n, v in pure:
print(f" {n}=={v}")
print()
if both:
print(f"Бинарные, wheels для обеих платформ ({len(both)}):")
for n, v in both:
print(f" {n}=={v}")
print()
if glibc_only:
print(f"Бинарные, ТОЛЬКО manylinux ({len(glibc_only)}):")
for n, v in glibc_only:
print(f" {n}=={v} ← на Alpine будет компилироваться")
print()
if unknown:
print(f"Не удалось определить ({len(unknown)}):")
for n in unknown:
print(f" {n}")
print()
print("═══ Рекомендация ═══\n")
binary_count = len(both) + len(glibc_only)
if glibc_only:
print(" python:3.13-slim")
print()
print(f" Причина: {len(glibc_only)} пакетов не имеют musllinux-wheel.")
print(" На Alpine они будут компилироваться из исходников:")
print(" - время сборки вырастет на 2-15 минут на пакет;")
print(" - потребуются gcc, musl-dev и заголовки (+200 MB в стадии сборки);")
print(" - возможны ошибки компиляции, специфичные для musl.")
elif binary_count == 0:
print(" python:3.13-alpine подойдёт")
print()
print(" Причина: все зависимости — чистый Python.")
print(" Экономия около 70 MB без риска компиляции.")
print(" python:3.13-slim тоже корректен, если размер некритичен.")
else:
print(" python:3.13-slim (по умолчанию) или python:3.13-alpine")
print()
print(f" Причина: все {binary_count} бинарных пакетов имеют wheels для обеих платформ.")
print(" Alpine даст меньший образ; slim — быстрее и привычнее в отладке.")
print(" Решайте измерением на своём проекте.")
print()
print(" Проверьте выбор фактической сборкой обоих вариантов:")
print(" docker build -f Dockerfile.slim -t app:slim .")
print(" docker build -f Dockerfile.alpine -t app:alpine .")
PY
Проверка на двух наборах:
chmod +x choose-base.sh
# набор 1: чистый Python
cat > pure.txt <<'EOF'
fastapi==0.141.1
httpx==0.28.1
EOF
./choose-base.sh pure.txt
echo
echo "════════════════════════════════"
echo
# набор 2: с бинарными расширениями
cat > binary.txt <<'EOF'
fastapi==0.141.1
psycopg2-binary==2.9.11
pandas==2.3.4
EOF
./choose-base.sh binary.txt
Ожидаемый вывод для первого набора:
═══ Анализ pure.txt: 2 пакетов ═══
Чистый Python (2) — работают везде одинаково:
fastapi==0.141.1
httpx==0.28.1
═══ Рекомендация ═══
python:3.13-alpine подойдёт
Причина: все зависимости — чистый Python.
Экономия около 70 MB без риска компиляции.
python:3.13-slim тоже корректен, если размер некритичен.
Для второго:
═══ Анализ binary.txt: 3 пакетов ═══
Чистый Python (1) — работают везде одинаково:
fastapi==0.141.1
Бинарные, wheels для обеих платформ (1):
pandas==2.3.4
Бинарные, ТОЛЬКО manylinux (1):
psycopg2-binary==2.9.11 ← на Alpine будет компилироваться
═══ Рекомендация ═══
python:3.13-slim
Причина: 1 пакетов не имеют musllinux-wheel.
На Alpine они будут компилироваться из исходников:
- время сборки вырастет на 2-15 минут на пакет;
- потребуются gcc, musl-dev и заголовки (+200 MB в стадии сборки);
- возможны ошибки компиляции, специфичные для musl.
Что делает скрипт практически полезным.
Он превращает выбор базового образа из вопроса вкуса в проверяемое решение. Обычный спор «Alpine меньше» против «Alpine медленнее» разрешается за десять секунд конкретным ответом для вашего набора зависимостей.
Ключевое различение — три категории, а не две. Пакет на чистом Python вообще не зависит от библиотеки C. Пакет с wheels для обеих платформ работает везде. И только третья категория — с manylinux, но без musllinux — делает Alpine дорогим.
Ограничение скрипта. Он проверяет только прямые зависимости из файла. Транзитивные могут добавить бинарные пакеты. Более точный, но медленный способ — собрать оба варианта и сравнить фактически; скрипт нужен как быстрый первый фильтр.
Проверка результата
docker run --rm python:3.13-slim sh -c 'ldd --version | head -1'
docker run --rm python:3.13-alpine sh -c 'ldd 2>&1 | head -1'
Убедитесь, что видите GLIBC и musl соответственно, и можете объяснить, почему это различие влияет на установку pandas.
Типичные ошибки
| Ошибка | Причина | Исправление |
|---|---|---|
| Alpine «потому что меньше» | Верно для Go, не для Python | Проверить наличие musllinux-wheels для своих зависимостей |
python:latest или python:3 | Кажется, что «всегда свежий» | Минорная версия сменится незаметно; указывать 3.13 |
Полный python:3.13 в production | Значение по умолчанию | ~900 MB инструментов сборки не нужны при работе |
| Переход на 3.14 сразу после выхода | Хочется актуальности | Wheels для новой минорной версии появляются с задержкой |
| Distroless без опыта отладки | Выглядит как правильный production | Нет оболочки; сначала освойте nsenter и /proc/<pid>/root |
psycopg2 вместо psycopg2-binary в разработке | Копируют из примеров | -binary не требует компиляции; для production выбор осознанный |
| Сравнение только размера образа | Самая заметная метрика | Учитывать также время сборки и время отладки |
| Alpine + multi-stage без проверки wheels | Кажется, что multi-stage решает всё | Компиляторы уйдут, но время сборки останется |
Контрольные вопросы
На понимание:
- Чем
muslотличается отglibcс точки зрения установки Python-пакетов? - Почему пакет на чистом Python одинаково работает на Alpine и на Debian?
- Что произойдёт при
pip install, если подходящего wheel нет? - Почему образ на Alpine может оказаться больше, чем на
slim? - Почему курс использует Python 3.13, а не 3.14?
На применение:
- Как узнать, есть ли
musllinux-wheel для пакета, не собирая образ? - Как посмотреть, какие теги платформы видит
pipв конкретном образе? - Как проверить, какая библиотека C используется в образе?
На диагностику:
- Сборка на Alpine падает с
error: command 'gcc' failed. Что произошло и какие два решения возможны? - После перехода на Alpine время сборки в CI выросло с 40 секунд до 12 минут. Как найти виновника?
Краткое резюме
- Варианты официального образа: полный (~1 GB),
slim(~126 MB),alpine(~55 MB). - Полный вариант содержит компиляторы и заголовки — в production они не нужны.
- Alpine использует
musl, остальные —glibc; это определяет доступность бинарных wheels. - Стандарт
manylinuxподдерживается почти всеми пакетами,musllinux— заметно реже. - При отсутствии wheel
pipкомпилирует из исходников: медленно и требует инструментов сборки. - Alpine оправдан для чистого Python и когда все зависимости имеют
musllinux-wheels. - Проверять наличие wheels нужно до выбора образа, а не после первой сборки.
- Distroless минимален и безопасен, но лишён оболочки — отладка требует отдельных навыков.
- Версия Python: текущая минус одна как значение по умолчанию, минорная версия указывается явно.
- Решение принимается измерением на своём наборе зависимостей, а не общим правилом.
Официальные источники
| Источник | Ссылка | Что подтверждает |
|---|---|---|
| Python official image | https://hub.docker.com/_/python | Варианты образа, предупреждение о musl и его влиянии на пакеты, поддерживаемые версии, база Debian Trixie |
| Docker Python language guide | https://docs.docker.com/language/python/ | Рекомендации по выбору образа для Python |
| Python Packaging: platform compatibility tags | https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/ | Формат тегов wheels, соответствие платформам |
| PEP 600 — manylinux | https://peps.python.org/pep-0600/ | Стандарт совместимости wheels для систем на glibc |
| PEP 656 — musllinux | https://peps.python.org/pep-0656/ | Стандарт совместимости wheels для систем на musl |
| pip: installation and wheels | https://pip.pypa.io/en/stable/cli/pip_install/ | Порядок выбора wheel или sdist, команда pip debug |
| Alpine Linux about | https://www.alpinelinux.org/about/ | Использование musl libc и BusyBox |
| Distroless images | https://github.com/GoogleContainerTools/distroless | Состав образов, отсутствие оболочки и пакетного менеджера |
| Building best practices | https://docs.docker.com/build/building/best-practices/ | Выбор минимального базового образа |
Навигация
Вернуться к разделу
Следующий материал → Управление зависимостями
Главное оглавление