Pull to refresh

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

Level of difficultyEasy
Reading time2 min
Views12K

Привет!

В очередной раз собирая образ 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.

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

Tags:
Hubs:
Total votes 11: ↑6 and ↓5+3
Comments11

Articles