January 8

Лучший менеджер зависимостей - это docker

Именно такую фразу мне сказал старший товарищ при одной из бесед, но тогда я его не понял. Был ли он прав?

Мой опыт

Спустя несколько лет работы с docker, я пришёл к выводу, что не имеет смысла выбирать между pipenv, poetry или conda, поскольку их использование внутри docker является затруднительным и требует плодить костыли, вместо того чтобы просто установить зависимости через pip.

Основная проблема заключается в том, что внутри контейнера вам никакое виртуальное окружение не нужно, а значит - мы затягиваем лишнюю зависимость, которая увеличит время сборки, зато локальная разработка без докера становится удобнее и приятнее. Только я, хоть убей, не пойму - какой смысл разрабатывать локально приложения на python без докера. Пожалуй, есть только один небольшой минус - нужно вписать новую зависимость в requirements.txt вручную. Но и этого можно избежать, если вы просто зайдёте в терминал контейнера, установите зависимость там и сделаете pip freeze в нужный файлик (разумеется, при условии, что зависимости попадают в volume с приложением).

Примеры

Всё познаётся в сравнении, поэтому рассмотрим несколько примеров конфигурации контейнера для django-проекта.

Вот как выглядит классический Dockerfile для django-проекта с использованием только pip:

# получаем image для нашего питона
FROM python:3.12

# устанавливаем рабочую директорию
WORKDIR /var/www/apps/django_core

# задаём переменные окружения, чтобы
# предотвратить ряд событий, а именно:
# создания .pyc файлов и использования
# питоном буффера для stdout/stderr
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# ставим зависимости
RUN pip install --upgrade pip
COPY ./requirements ./requirements
RUN pip install -r requirements/production.txt

А вот, как он будет выглядеть, если мы будем использовать, к примеру, pipenv:

# получаем image для нашего питона
FROM python:3.12

# устанавливаем рабочую директорию
WORKDIR /var/www/apps/django_core

# задаём переменные окружения, чтобы
# предотвратить ряд событий, а именно:
# создания .pyc файлов и использования
# питоном буффера для stdout/stderr
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# ставим зависимости
RUN pip install --upgrade pip
RUN pip install pipenv
RUN pipenv run pip freeze > ./requirements/production.txt
COPY ./requirements ./requirements
RUN pip install -r requirements/production.txt

"Выглядит страшно нелепо" - думал я, однако, решил себя перепроверить и наткнулся на статью. В ней описывается такой Dockerfile:

FROM python:3.8

RUN pip install pipenv

ENV PROJECT_DIR /usr/local/src/webapp

WORKDIR ${PROJECT_DIR}

COPY Pipfile Pipfile.lock ${PROJECT_DIR}/

RUN pipenv install --system --deploy

Если немного видоизменить этот Dockerfile под мои нужды, получится следующий вариант:

# получаем image для нашего питона
FROM python:3.12

# устанавливаем рабочую директорию
WORKDIR /var/www/apps/django_core

# задаём переменные окружения, чтобы
# предотвратить ряд событий, а именно:
# создания .pyc файлов и использования
# питоном буффера для stdout/stderr
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# ставим зависимости
RUN pip install --upgrade pip
RUN pip install pipenv
COPY Pipfile Pipfile.lock ./
RUN pipenv install --system --deploy

В таком виде, он не получается сильно длиннее, а время сборки, хоть и увеличивается, но незначительно, что позволяет не обращать на это большого внимания и получить все плюсы от локального управления зависимостями с использованием более продвинутого менеджера зависимостей, чем стандартный pip.

Если вы думаете, что с poetry будет всё также легко, то вы не до конца правы, я наткнулся на целую дискуссию на github по поводу того, как правильно использовать poetry внутри докера. И те докерфайлы, которые мне удалось вычитать выглядит достаточно монструозно, чтобы отказываться вручную забежать в контейнер и написать там pip freeze, на мой взгляд. Но если вам всё-таки интересно, как подружить poetry с докером, то вы можете почитать этот пост от энтузиаста.

Вместо заключения

Хочется сказать, что вы вольны для своих проектов выбирать те инструменты, которые кажутся вам наиболее подходящими, но если на проекте кроме вас работает ещё несколько человек, то подходы нужно стандартизировать и в таком случае, я бы склонялся больше к использованию pip без дополнительных надстроек.