Python в Docker — собираем образ сами

09f47c56d471300d4f483afb0e31b5cf

Привет!

В очередной раз собирая образ Docker своего бота для Телеграмм и используя в качестве базы официальный образ python:3.12.2-alpine3.19, обратил внимание на то, что docker scout показывает наличие свежей уязвимости в pip . Я бы не сказал, что она как‑то влияет на мое приложение, но сам факт наличия потенциальной уязвимости «на борту» контейнера с приложением, которое работает под рутом и с проброшенным сокетом Docker (НЕ лучшая практика! ) натолкнул меня на мысль, как можно минимизировать этот риск?

Прежде чем мы начнем, небольшое соглашение:

Я не профессиональный программист, курсов Яндекс Практикума не заканчивал, пишу для таких же новичков в Docker и Python как и я)

Путей существует на самом деле немало, для конкретно моего приложения, я решил изучить вопрос сборки образа на базе самого минимального базового образа Linux — Alpine Linux. Для уменьшения поверхности атаки. И вот тут я столкнулся с парочкой моментов:

Alpine не содержит в себе Python

Ну хорошо, можно же установить, подумал я. Можно то оно можно, вот только размер такого контейнера будет довольно большим, достаточно посмотреть на собранный на базе Alpine Linux python:3.12.2-alpine3.19. В ветке Main репозитория Alpine Linux доступен Python версии 3.11.8, что мне не подходит. Эти моменты можно безболезненно обойти используя для сборки контейнера многоэтапную сборку, где на первом этапе скомпилировать и установить все зависимости:

# First stage
FROM python:3.12.2-alpine3.19 AS builder
COPY requirements.txt .

# Install psutil deps
RUN apk --no-cache add gcc python3-dev musl-dev linux-headers

# Install dependencies to the venv path
RUN python3 -m venv --without-pip venv
RUN pip install --no-cache --target="/venv/lib/python3.12/site-packages" -r requirements.txt

Здесь мы за основу берем все тот же официальный образ python:3.12.2-alpine3.19, копируем файл с зависимостями приложения, устанавливаем все зависимости для сборки пакетов Python (в моем случае это необходимо для сборки psutil) и, выполнив инициализацию виртуального окружения, устанавливаем все зависимости приложения.

Что именно в Python копировать из первого этапа сборки?

Для меня это был самый сложный момент — поиск ничего внятного не подсказывал. Пришлось изучать структуру каталогов Python и методом проб и ошибок выяснил что для корректной работы Python 3.12.2 в образе Alpine Linux достаточно вот такой структуры:

/usr/local/bin/python3
/usr/local/bin/python3.12
/usr/local/lib/python3.12
/usr/local/lib/libpython3.12.so.1.0
/usr/local/lib/libpython3.so

Теперь мы можем скопировать из первого этапа сборки во второй этап нужную нам структуру Python:

# Second unnamed stage
FROM alpine:3.19.1
...
# Сopy only the necessary python files and directories from first stage
COPY --from=builder /usr/local/bin/python3 /usr/local/bin/python3
COPY --from=builder /usr/local/bin/python3.12 /usr/local/bin/python3.12
COPY --from=builder /usr/local/lib/python3.12 /usr/local/lib/python3.12
COPY --from=builder /usr/local/lib/libpython3.12.so.1.0 /usr/local/lib/libpython3.12.so.1.0
COPY --from=builder /usr/local/lib/libpython3.so /usr/local/lib/libpython3.so
...

На выходе получаем в моем случае работающее приложение на Python, собранное на основе минимального образа Alpine Linux, у которого по мнению docker scout нет известных уязвимостей.

Полный Dockerfile бота доступен на Github.

А какие практики используете Вы?

© Habrahabr.ru