Building Slim OCI Images Using Python+Poetry

Motivation

poetry is a great library for Python packaging and dependency management. In particular, it protrudes with its exhaustive dependency resolution algorithm. Suppose that a component A depends on components B and C, where B also depends on C. So far so good! Now add 6 version release specifiers (~=, ==, !=, <=, >=, <, >, ===): These version specifiers place constraints on the version of dependencies needed in order to build or run the desired software and can get pretty complex (e.g. ~= 0.9, >= 1.0, != 1.3.4.*, < 2.0). Without going into detail of the intricacies of the version specifiers, note that poetry takes great care to resolve all dependencies, which are usually versioned using Semantic Versioning. Furthermore, poetry locks all dependencies in a poetry.lock file by referencing their respective hashes, to guarantee a reproducible dependency graph.

Dependency graph

poetry is great software and brings to Python development what more modern languages such as Go or Rust brought straight from the beginning.

Building an OCI image from a Dockerfile

The Open Container Initiative standardizes several specifications around containers. Amongst them is the Image Specification, which outlines how to package a filesystem bundle, that can be executed by container runtimes (e.g. containerd). Or as people say colloquially: OCI images are Docker container images.

Our goal is to build such a container with a Python application and its dependencies, as fetched by poetry. The approach is simple: Using a multi-stage build, we create a /app directory with our src directory and a .venv directory, which includes all dependencies. We then copy the .venv directory to the prod OCI image, where poetry does not reside. This is shown below (note the use of poetry config virtualenvs.in-project true to force the creation of a .venv folder in the current directory).

FROM python:3.11.1-alpine3.17 AS builder

RUN apk add --no-cache build-base libffi-dev openssl-dev curl
RUN curl -sSL https://install.python-poetry.org | python3 -
WORKDIR /app
COPY poetry.lock pyproject.toml README.md .
COPY src ./src
RUN /root/.local/bin/poetry config virtualenvs.in-project true
RUN /root/.local/bin/poetry install

FROM python:3.11.1-alpine3.17
RUN mkdir -p /app/src
COPY --from=builder /app/.venv/ /app/.venv
COPY src /app/src
EXPOSE 8000
WORKDIR /app/src
CMD ["../.venv/bin/python", "app.py"]

Result

Interesting alternatives to consider are buildah and particularly nixpkgs with pkgs.dockerTools. I might write a post about these in the future.