Привет!
В очередной раз собирая образ Docker своего бота для Telegram и используя в качестве базы официальный образ python:3.12.2-alpine3.19
, обратил внимание на то, что docker scout
показывает наличие свежей уязвимости в pip
. Я бы не сказал, что она как‑то влияет на мое приложение, но сам факт наличия потенциальной уязвимости «на борту» контейнера с приложением, которое работает под рутом и с проброшенным сокетом Docker (НЕ лучшая практика!) натолкнул меня на мысль, как можно минимизировать этот риск?
Прежде чем мы начнем, небольшое соглашение:
Я не профессиональный программист, курсов Яндекс Практикума не заканчивал, пишу для таких же новичков в Docker и Python как и я)
Путей существует на самом деле немало, для конкретно моего приложения, я решил изучить вопрос сборки образа на базе самого минимального базового образа Linux - Alpine Linux. Для уменьшения поверхности атаки. И вот тут я столкнулся с парочкой моментов:
Alpine не содержит в себе Python
Ну хорошо, можно же установить, подумал я. Можно то оно можно, вот только размер такого контейнера будет довольно большим, достаточно посмотреть на образ, который собран с использованием в качестве базы 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 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 копировать из первого этапа сборки?
Для меня это был самый сложный момент - поиск ничего внятного не подсказывал. Пришлось изучать структуру каталогов 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.
А какие практики используете Вы?