Dockerizing Python Poetry Applications

This is an example of Dockerizing Python Poetry Applications. I wanted to share a simple Dockerfile example that tries to follow the best Dockerfile packaging practices: non-root user, alpine base, and multistage builds. Poetry is a new dependency management tool for Python. Poetry simplifies the process of managing dependencies and packaging Python projects by providing a user-friendly interface for specifying dependencies accurately, generating reproducible lock files, and creating standalone executable or wheel files. Its streamlined workflow and compatibility with popular package managers like pip make it a great choice for Python developers looking to simplify their development and deployment processes.

Some Poetry advantages include:

  • repeatable builds – stores the version of every single dependency, including dependencies of other dependencies, in poetry.lock files. This makes repeatable builds easy to do.
  • simplified virtual environments – Poetry manages the venv for you, so you don’t need to worry about separate tooling
  • simplified PyPI publishing (poetry publish -r private-repo)
  • Easily build and package your projects with a single command and remove the crust files like setup.cfg and spare yourself from dealing with setuptools tooling
  • modern and convenient cli
  • it’s probably going to feel very familiar to npm and composer users 🙂
  • others, see official docs: https://python-poetry.org/docs/

    Python poetry Dokerfile example

    Without further ado, here’s an example of Dockerizing Python Poetry Applications using the arbitrary non-root user lind to dockerize some python package named mirostack.

    Here we are packaging the Dockerfile using several best practices:

    • a non-root user is one of the best practices when building Dockerfiles
    • This Dockerfile is also using the multi-stage build format as this is also one of the Dockerfile best practices and helps us minimize the resultant Dockerfile size and only include the code we need for the application to run.
    • Using an alpine python Docekerfile base image as a base to help reduce the resultant image size

    FROM python:3.11-alpine as base
     
    ## Step 1: configure base
    ##=============================================================================
    # PYTHONUNBUFFERED=1: stdout and stderr streams are sent straight to terminal (e.g. your container log) without
    # being first buffered #and that you can see the output of your application in real time.
    # PYTHONHASHSEED: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHASHSEED
    # PYTHONFAULTHANDLER: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONFAULTHANDLER
    ENV PYTHONFAULTHANDLER=1 \
        PYTHONHASHSEED=random \
        PYTHONUNBUFFERED=1
     
    # Alpine: add non root user and group
    RUN addgroup -g 1000 lind
    RUN adduser -u 1000 -G lind -h /home/lind -D lind
     
    # Create app placeholder dir
    RUN mkdir /app && chown -R lind:lind /app
     
    WORKDIR /app
     
     
     
    ## Step 2:
    ##=============================================================================
    FROM base as builder
     
    ENV PIP_DEFAULT_TIMEOUT=100 \
        PIP_DISABLE_PIP_VERSION_CHECK=1 \
        PIP_NO_CACHE_DIR=1 \
        POETRY_VERSION=1.3.2
     
    #Since you use non-root user, poetry will be installed under /home/lind/.local/bin/poetry
    #To make sure we can use it -> lets add that into path.
    ENV PATH="/home/lind/.local/bin:${PATH}"
     
    #Install poetry using pip
    RUN pip install "poetry==$POETRY_VERSION"
    COPY pyproject.toml poetry.lock ./
     
    #Copy our actual package source code then build the package
    COPY mirostack ./mirostack
    RUN poetry config virtualenvs.in-project true && \
        poetry install --only=main --no-root --no-interaction --no-ansi && \
        poetry build
     
     
     
     
    ## Step 3:
    ##=============================================================================
    FROM base as final
    #ARG ENV_NAME
     
    #Copy the dependencies and actual distribution package
    COPY --from=builder /app/.venv ./.venv
    COPY --from=builder /app/dist .
    
    #Here -> we are comying some environmental config file. Your
    #user-case may not need this. If you need to environmentalize your docker images -> 
    #then make sure to include some ENV_NAME argument and .env file into your container build process
    #COPY stack/env-${ENV_NAME}/.env .
     
    RUN chown -R lind:lind /app
     
    #Switch to this user 
    USER lind
     
    #Install our distribution package (we built python wheel in a previous step)
    RUN ./.venv/bin/pip install *.whl
     
    #This will initialize the python's virtual env (so we don't need to call source .venv/bin/activate)
    ENV PATH="/app/.venv/bin:${PATH}"
    ENV VIRTUAL_ENV="/app/.venv"
     
     #Invoke the application
    CMD ["python", "/app/.venv/lib/python3.11/site-packages/mirostack/main.py"]
    

    Note that internally this Dockerfile will create the mirostack python package from your code which is then installed in the final image. If your python package code is structured as in the example on the poetry site: https://python-poetry.org/docs/basic-usage/#project-setup -> this Dockerfile should just work automatically.

    Fin. Thanks for reading.

    1 thought on “Dockerizing Python Poetry Applications”

    Leave a Comment