Building Slim OCI Images Using Python+Poetry


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 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 | python3 -
COPY poetry.lock pyproject.toml .
COPY src ./src
RUN /root/.local/bin/poetry config 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
WORKDIR /app/src
CMD ["../.venv/bin/python", ""]


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