Integrando la poesía de Python con Docker

15 minutos de lectura

avatar de usuario
Alex Bodea

¿Me puede dar un ejemplo de un Dockerfile en el que puedo instalar todos los paquetes que necesito de poetry.lock y pyproject.toml en mi imagen/contenedor desde Docker?

avatar de usuario
sobolevn

Hay varias cosas a tener en cuenta al usar poetry Juntos con docker.

Instalación

Forma oficial de instalar poetry es a través de:

curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -

Esta manera permite poetry y sus dependencias para ser aislado de sus dependencias. Pero, bajo mi punto de vista, no es muy bueno por dos razones:

  1. poetry La versión puede obtener una actualización y romperá su compilación. En este caso, puede especificar POETRY_VERSION Variable ambiental. El instalador lo respetará
  2. No me gusta la idea de canalizar cosas de Internet a mis contenedores sin ninguna protección contra posibles modificaciones de archivos.

Entonces, uso pip install 'poetry==$POETRY_VERSION'. Como puede ver, todavía recomiendo fijar su versión.

Además, fije esta versión en su pyproject.toml también:

[build-system]
# Should be the same as `$POETRY_VERSION`:
requires = ["poetry>=1.0"]
build-backend = "poetry.masonry.api"

Lo protegerá de la discrepancia de versión entre su local y docker entornos.

Dependencias de almacenamiento en caché

Queremos almacenar en caché nuestros requisitos y solo reinstalarlos cuando pyproject.toml o poetry.lock los archivos cambian. De lo contrario, las compilaciones serán lentas. Para lograr que la capa de caché funcione, debemos poner:

COPY poetry.lock pyproject.toml /code/

Después de la poetry está instalado, pero antes de que se agreguen otros archivos.

Entorno virtual

Lo siguiente a tener en cuenta es virtualenv creación. No lo necesitamos en docker. Ya está aislado. Entonces, usamos poetry config virtualenvs.create false ajuste para apagarlo.

Desarrollo vs Producción

Si usas lo mismo Dockerfile Tanto para el desarrollo como para la producción como lo hago yo, deberá instalar diferentes conjuntos de dependencias en función de alguna variable de entorno:

poetry install $(test "$YOUR_ENV" == production && echo "--no-dev")

De esta manera $YOUR_ENV controlará qué conjunto de dependencias se instalará: todo (predeterminado) o solo producción con --no-dev bandera.

También es posible que desee agregar algunas opciones más para una mejor experiencia:

  1. --no-interaction no hacer preguntas interactivas
  2. --no-ansi bandera para hacer que su salida sea más fácil de registrar

Resultado

Terminarás con algo similar a:

FROM python:3.6.6-alpine3.7

ARG YOUR_ENV

ENV YOUR_ENV=${YOUR_ENV} \
  PYTHONFAULTHANDLER=1 \
  PYTHONUNBUFFERED=1 \
  PYTHONHASHSEED=random \
  PIP_NO_CACHE_DIR=off \
  PIP_DISABLE_PIP_VERSION_CHECK=on \
  PIP_DEFAULT_TIMEOUT=100 \
  POETRY_VERSION=1.0.0

# System deps:
RUN pip install "poetry==$POETRY_VERSION"

# Copy only requirements to cache them in docker layer
WORKDIR /code
COPY poetry.lock pyproject.toml /code/

# Project initialization:
RUN poetry config virtualenvs.create false \
  && poetry install $(test "$YOUR_ENV" == production && echo "--no-dev") --no-interaction --no-ansi

# Creating folders, and files for a project:
COPY . /code

Puede encontrar un ejemplo de la vida real completamente funcional aquí: wemake-django-plantilla

Actualización el 2019-12-17

  • Actualizar poetry a 1.0

  • Los lectores de esta respuesta pueden Atención para obtener información sobre las compilaciones de varias etapas de Docker. Sé que, en mi caso, las compilaciones de varias etapas simplificaron enormemente el proceso de las imágenes de la ventana acoplable base frente a la prueba frente a la aplicación. Ver también esta publicación que no es específico de la poesía pero muestra una razón puede que Considere continuar usando virtualenv dentro de Docker, cuando realice compilaciones de varias etapas. (Aún no me he probado a mí mismo, solo he adoptado poetry recientemente.)

    – m_flor

    14 de marzo de 2019 a las 0:54

  • @sobolevn la única preocupación con pip install poetry es que las dependencias de Poetry pueden entrar en conflicto con las dependencias de la aplicación.

    – Rob subvención

    9 de junio de 2019 a las 13:21

  • poetry config virtualenvs.create false no funciona en 1.0.0. Usar RUN POETRY_VIRTUALENVS_CREATE=false poetry install en cambio.

    – JerryDDG

    18 de diciembre de 2019 a las 3:14

  • En realidad, instalar poesía con pip install hacer conflicto con las dependencias de la aplicación, ya que las dependencias de poesía también tienen sus propias dependencias. Está absolutamente bajo el control del desarrollador. Usando este método, siempre se recomienda usar pip install --ignore-installed. Tampoco me gusta canalizar algo de Internet directamente en el caparazón. Sin mencionar que requiere curl, wget o cualquier otra cosa. Pero, si decidiste hacerlo, hay --version opción de get-poetry.py guion.

    – Antonio

    28 de agosto de 2020 a las 14:13

  • Este método cayó en su propia cara para mí: en mi proyecto pyproject.toml, lo tenía todo configurado con normalidad. Sin embargo, pip install poetry (en Python 3.7) instala appdirs como una dependencia de poetry, Como era la intención. Pero al correr con config virtualenvs.create false, poetry ejecuta “bare-metal”, y elimina appdirs otra vez (Removing appdirs (1.4.4), mientras se instalan bien las dependencias normales del proyecto). Esto es porque appdirs no figuraba en pyproject.toml (porque ¿por qué lo haría?). Volví a usar entornos virtuales nuevamente, de modo que poetry no quita appdirs.

    – Álex Povel

    1 de marzo de 2021 a las 11:30

avatar de usuario
Claudio

Compilación de Docker de varias etapas con Poetry y venv

No deshabilite la creación de virtualenv. Virtualenvs tiene un propósito en las compilaciones de Docker, porque proporcionan una forma elegante de aprovechar las compilaciones de varias etapas. En pocas palabras, su etapa de compilación instala todo en el virtualenv, y la etapa final simplemente copia el virtualenv en una imagen pequeña.

Usar poetry export e instale primero sus requisitos anclados, antes de copiar su código. Esto le permitirá usar el caché de compilación de Docker y nunca reinstalar las dependencias solo porque cambió una línea en su código.

No utilice poetry install para instalar su código, porque realizará una instalación editable. En su lugar, utiliza poetry build para construir una rueda, y luego instálela en su virtualenv. (Gracias a PEP 517todo este proceso también podría realizarse con un simple pip install .pero debido a construir aislamiento terminarías instalando otra copia de Poetry.)

Aquí hay un Dockerfile de ejemplo que instala una aplicación Flask en una imagen de Alpine, con una dependencia de Postgres. Este ejemplo utiliza un script de punto de entrada para activar virtualenv. Pero, en general, debería estar bien sin un script de punto de entrada porque simplemente puede hacer referencia al binario de Python en /venv/bin/python en tus CMD instrucción.

Dockerfile

FROM python:3.7.6-alpine3.11 as base

ENV PYTHONFAULTHANDLER=1 \
    PYTHONHASHSEED=random \
    PYTHONUNBUFFERED=1

WORKDIR /app

FROM base as builder

ENV PIP_DEFAULT_TIMEOUT=100 \
    PIP_DISABLE_PIP_VERSION_CHECK=1 \
    PIP_NO_CACHE_DIR=1 \
    POETRY_VERSION=1.0.5

RUN apk add --no-cache gcc libffi-dev musl-dev postgresql-dev
RUN pip install "poetry==$POETRY_VERSION"
RUN python -m venv /venv

COPY pyproject.toml poetry.lock ./
RUN poetry export -f requirements.txt | /venv/bin/pip install -r /dev/stdin

COPY . .
RUN poetry build && /venv/bin/pip install dist/*.whl

FROM base as final

RUN apk add --no-cache libffi libpq
COPY --from=builder /venv /venv
COPY docker-entrypoint.sh wsgi.py ./
CMD ["./docker-entrypoint.sh"]

docker-entrypoint.sh

#!/bin/sh

set -e

. /venv/bin/activate

while ! flask db upgrade
do
     echo "Retry..."
     sleep 1
done

exec gunicorn --bind 0.0.0.0:5000 --forwarded-allow-ips="*" wsgi:app

wsgi.py

import your_app

app = your_app.create_app()

  • Actualización: se lanzó Poetry 1.0.0. El lanzamiento previo ya no es necesario para los requisitos de exportación.

    – Claudio

    13 de diciembre de 2019 a las 16:19

  • Consulte también la excelente guía de empaquetado de Docker para Python de Itamar Turner-Trauring: pythonspeed.com/docker. Siguiendo su consejo, esta respuesta probablemente debería actualizarse para usar una imagen delgada de Debian en lugar de Alpine.

    – Claudio

    4 de mayo de 2020 a las 14:19

  • “No use la instalación de poesía para instalar su código, ya que realizará una instalación editable”. Puede deshabilitar este comportamiento con --no-root bandera. Ver un problema cerrado de Github aquí.

    – Radzak

    5 de mayo de 2020 a las 14:36

  • Eso está muy bien, excepto que hay momentos en los que poetry export -f requirements.txt genera archivos de requisitos no válidos: las mismas entradas están duplicadas. Esto parece estar relacionado con intentar admitir diferentes versiones de Python.

    –Matthew Schinckel

    4 de junio de 2020 a las 7:30

  • no tienes que usar . /venv/bin/activatees suficiente en el Dockerfile para usar ENV PATH="/venv/bin:${PATH}" y ENV VIRTUAL_ENV="/venv" lo que significa que puede tener un punto de entrada/cmd en línea y aún usará el venv.

    – Duncan

    12 de julio de 2021 a las 16:47

Esta es una revisión menor de la respuesta proporcionada por @Claudio, que usa el nuevo poetry install --no-root característica como lo describe @sobolevn en su respuesta.

Para forzar poesía para instalar dependencias en un entorno virtual específico, primero debe habilitarlo.

. /path/to/virtualenv/bin/activate && poetry install

Por lo tanto, al agregar estos en la respuesta de @Claudio, tenemos

FROM python:3.9-slim as base

ENV PYTHONFAULTHANDLER=1 \
    PYTHONHASHSEED=random \
    PYTHONUNBUFFERED=1

RUN apt-get update && apt-get install -y gcc libffi-dev g++
WORKDIR /app

FROM base as builder

ENV PIP_DEFAULT_TIMEOUT=100 \
    PIP_DISABLE_PIP_VERSION_CHECK=1 \
    PIP_NO_CACHE_DIR=1 \
    POETRY_VERSION=1.1.3

RUN pip install "poetry==$POETRY_VERSION"
RUN python -m venv /venv

COPY pyproject.toml poetry.lock ./
RUN . /venv/bin/activate && poetry install --no-dev --no-root

COPY . .
RUN . /venv/bin/activate && poetry build

FROM base as final

COPY --from=builder /venv /venv
COPY --from=builder /app/dist .
COPY docker-entrypoint.sh ./

RUN . /venv/bin/activate && pip install *.whl
CMD ["./docker-entrypoint.sh"]

Si necesita usar esto para fines de desarrollo, agregue o elimine el --no-dev reemplazando esta línea

RUN . /venv/bin/activate && poetry install --no-dev --no-root

a algo como esto como se muestra en la respuesta de @sobolevn

RUN . /venv/bin/activate && poetry install --no-root $(test "$YOUR_ENV" == production && echo "--no-dev")

después de agregar la declaración de variable de entorno adecuada.

El ejemplo usa debian-slim como base, sin embargo, adaptar esto a una imagen basada en alpine debería ser una tarea trivial.

  • Entonces, realmente me gusta esta respuesta, pero ¿cómo lidiaría con las dependencias de la ruta local?

    – DUWUDA

    3 de febrero de 2021 a las 7:34

  • ¿Qué quiere decir con dependencias de rutas locales?

    – Jeffrey04

    3 de febrero de 2021 a las 12:09

  • Las dependencias de ruta son útiles en las configuraciones de monorepo, donde tiene bibliotecas compartidas en otro lugar de su repositorio, consulte los documentos

    – Luper Rouch

    4 de febrero de 2021 a las 14:36

  • agregar el respectivo COPY comandos antes de la RUN poetry install o RUN poetry build ¿Supongo? mi respuesta (así como las referenciadas) prácticamente solo replica la configuración en el contenedor, solo que configuramos explícitamente el venv para que sea /venv/si la configuración en el contenedor es idéntica a su configuración de trabajo, técnicamente todo debería funcionar bien, solo piense cómo replicaría la configuración en otro lugar sin la ventana acoplable y ajustaría el Dockerfile en consecuencia.

    – Jeffrey04

    5 de febrero de 2021 a las 3:00


  • @Jeffrey04 COPY el paquete local en no funciona para mí. yo obtengo pip._vendor.pkg_resources.RequirementParseError: Invalid URL: my-package durante el comando RUN . /venv/bin/activate && pip install *.whl

    – kellposible

    4 de diciembre de 2021 a las 20:52


avatar de usuario
miguelvargasf

TL;DR

he podido configurar poetry para Django proyecto usando postgres. Después de investigar un poco, terminé con lo siguiente Dockerfile:

FROM python:slim

# Keeps Python from generating .pyc files in the container
ENV PYTHONDONTWRITEBYTECODE 1
# Turns off buffering for easier container logging
ENV PYTHONUNBUFFERED 1

# Install and setup poetry
RUN pip install -U pip \
    && apt-get update \
    && apt install -y curl netcat \
    && curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
ENV PATH="${PATH}:/root/.poetry/bin"

WORKDIR /usr/src/app
COPY . .
RUN poetry config virtualenvs.create false \
  && poetry install --no-interaction --no-ansi

# run entrypoint.sh
ENTRYPOINT ["/usr/src/app/entrypoint.sh"]

Este es el contenido de entrypoint.sh:

#!/bin/sh

if [ "$DATABASE" = "postgres" ]
then
    echo "Waiting for postgres..."

    while ! nc -z $SQL_HOST $SQL_PORT; do
      sleep 0.1
    done

    echo "PostgreSQL started"
fi

python manage.py migrate

exec "$@"

Explicación detallada

Algunos puntos a notar:

  • He decidido usar slim en vez de alpine como etiqueta para el python imagen porque aunque alpine Se supone que las imágenes reducen el tamaño de las imágenes de Docker y aceleran la construcción, con Python, en realidad puede terminar con una imagen un poco más grande y eso toma un tiempo para construir (leer Este artículo para más información).

  • El uso de esta configuración genera contenedores más rápido que el uso de la imagen alpina porque no necesito agregar algunos paquetes adicionales para instalar los paquetes de Python correctamente.

  • estoy instalando poetry directamente desde la URL proporcionada en la documentación. Soy consciente de las advertencias proporcionadas por sobolevn. Sin embargo, considero que es mejor a largo plazo usar la última versión de poetry por defecto que confiar en una variable de entorno que debo actualizar periódicamente.

  • Actualización de la variable de entorno PATH Es crucial. De lo contrario, obtendrá un error que dice que no se encontro poesia.

  • Las dependencias se instalan directamente en el intérprete de python del contenedor. no crea poetry para crear un entorno virtual antes de instalar las dependencias.

En caso de que necesite el alpine versión de esto Dockerfile:

FROM python:alpine

# Keeps Python from generating .pyc files in the container
ENV PYTHONDONTWRITEBYTECODE 1
# Turns off buffering for easier container logging
ENV PYTHONUNBUFFERED 1

# Install dev dependencies
RUN apk update \
    && apk add curl postgresql-dev gcc python3-dev musl-dev openssl-dev libffi-dev

# Install poetry
RUN pip install -U pip \
    && curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
ENV PATH="${PATH}:/root/.poetry/bin"

WORKDIR /usr/src/app
COPY . .
RUN poetry config virtualenvs.create false \
  && poetry install --no-interaction --no-ansi

# run entrypoint.sh
ENTRYPOINT ["/usr/src/app/entrypoint.sh"]

Note que el alpine la versión necesita algunas dependencias postgresql-dev gcc python3-dev musl-dev openssl-dev libffi-dev para funcionar correctamente.

avatar de usuario
maciek

Esa es la configuración mínima que funciona para mí:

FROM python:3.7

ENV PIP_DISABLE_PIP_VERSION_CHECK=on

RUN pip install poetry

WORKDIR /app
COPY poetry.lock pyproject.toml /app/

RUN poetry config virtualenvs.create false
RUN poetry install --no-interaction

COPY . /app

Tenga en cuenta que no es tan seguro como la configuración de @sobolevn.

Como trivia agrego eso si las instalaciones editables serán posibles para pyproject.toml proyectosse podrían eliminar una línea o dos:

FROM python:3.7

ENV PIP_DISABLE_PIP_VERSION_CHECK=on

WORKDIR /app
COPY poetry.lock pyproject.toml /app/

RUN pip install -e .

COPY . /app

  • Si el caso de su proyecto también contiene un módulo de Python mymodule que le gustaría instalar, como lo hace Poetry de forma predeterminada si encuentra uno, debe crear una versión ficticia como esta antes de ejecutar la instalación de poesía: RUN mkdir /app/mymodule && touch /app/mymodule/__init__.py. Esto funciona porque Poetry instala este tipo de módulos usando pip -e, que solo crea un enlace simbólico. Esto significa que todo funciona como se esperaba cuando los módulos reales se copian sobre él en el paso final. (De acuerdo con las modificaciones, este es un comentario y no una edición; intente incorporarlo en la publicación si no está de acuerdo).

    – Frankie Robertson

    23 de abril de 2019 a las 10:37


avatar de usuario
funky-futuro

Aquí hay un ejemplo simplificado donde primero se agrega a una imagen una capa con las dependencias (que solo se construye cuando estas cambian) y luego una con el código fuente completo. Ajuste poetry para instalar en el mundo site-packages deja un artefacto de configuración que también podría eliminarse.

FROM python:alpine

WORKDIR /app

COPY poetry.lock pyproject.toml ./
RUN pip install --no-cache-dir --upgrade pip \
 && pip install --no-cache-dir poetry \
 \
 && poetry config settings.virtualenvs.create false \
 && poetry install --no-dev \
 \
 && pip uninstall --yes poetry \

COPY . ./

  • Si el caso de su proyecto también contiene un módulo de Python mymodule que le gustaría instalar, como lo hace Poetry de forma predeterminada si encuentra uno, debe crear una versión ficticia como esta antes de ejecutar la instalación de poesía: RUN mkdir /app/mymodule && touch /app/mymodule/__init__.py. Esto funciona porque Poetry instala este tipo de módulos usando pip -e, que solo crea un enlace simbólico. Esto significa que todo funciona como se esperaba cuando los módulos reales se copian sobre él en el paso final. (De acuerdo con las modificaciones, este es un comentario y no una edición; intente incorporarlo en la publicación si no está de acuerdo).

    – Frankie Robertson

    23 de abril de 2019 a las 10:37


avatar de usuario
bneijt

Creé una solución usando un paquete de bloqueo (paquete que depende de todas las versiones en el archivo de bloqueo). Esto da como resultado una instalación limpia solo de pip sin archivos de requisitos.

Los pasos son: construya el paquete, construya el paquete de bloqueo, copie ambas ruedas en su contenedor, instale ambas ruedas con pip.

La instalación es: poetry add --dev poetry-lock-package

Los pasos fuera de la construcción de la ventana acoplable son:

poetry build
poetry run poetry-lock-package --build

Entonces tu Dockerfile debería contener:

FROM python:3-slim

COPY dist/*.whl /

RUN pip install --no-cache-dir /*.whl \
    && rm -rf /*.whl

CMD ["python", "-m", "entry_module"]

  • Solución perfecta. mi comentario original sobre el código fuente de python es incorrecto, pip instalaría todo en los paquetes del sitio.

    – kakarukeys

    22 de junio de 2021 a las 4:25


¿Ha sido útil esta solución?