Poetry is a great build system.
And in 2023, I believe, no one should use the pip for a private Python codebase.

Getting it right inside Docker is a different issue, however.

Consider a simple Flask-based web server as an example

# Install poetry
$ pip3 install poetry==1.7.1
$ poetry --version
Poetry (version 1.7.1)

# Create a sample package
$ poetry init --python=~3.10 --name=src --description='Flask Hello world' --dependency=Flask@3.0.0 --author='Ashish' --license='Apache 2.0' --no-interaction
$ poetry install
$ touch README.md
$ mkdir src

# Create a file src/server.py in your favorite editor
$ cat src/server.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"

if __name__ == "__main__":
app.run()

Let’s finish the build process
Now, let’s add a simple Dockerfile titled Dockerfile1

FROM python:3.10-slim as base

# Install Poetry
RUN pip3 install poetry==1.7.1
WORKDIR /app
COPY pyproject.toml poetry.lock /app
# Install dependencies
RUN poetry install
COPY src /app/src

ENTRYPOINT ["poetry", "run", "python", "src/server.py"]

And let’s build and check its size

$ docker build -f Dockerfile1 -t example1 . && docker image inspect example1 --format='{{.Size}}' | numfmt --to=iec-i
235Mi

We don’t need poetry in the final build, so, we can save space via multi-stage docker builds.

Consider following the multi-stage docker file Dockerfile2

FROM python:3.10-slim as builder

# Install Poetry
RUN pip3 install poetry==1.7.1
WORKDIR /app
COPY pyproject.toml poetry.lock /app

# virtual env is created in "/app/.venv" directory
ENV POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1 \
POETRY_VIRTUALENVS_CREATE=true \
POETRY_CACHE_DIR=/tmp/poetry_cache
# Install dependencies
RUN --mount=type=cache,target=/tmp/poetry_cache poetry install --only main --no-root
RUN poetry install

FROM python:3.10-slim as runner
COPY src /app/src
COPY --from=builder /app/.venv /app/.venv
ENV PATH="/app/.venv/bin:$PATH"

ENTRYPOINT ["/app/.venv/bin/python", "src/server.py"]

And the result

$ docker build -f Dockerfile2 -t example1 . && docker image inspect example1 --format='{{.Size}}' | numfmt --to=iec-i
158Mi

That’s an extra 77Mi (33%) of saving while reducing the attack surface of the docker image!