A use case for multi-stage builds

Lukasz
3 min readSep 25, 2023

Photo by Library of Congress on Unsplash

Upon revisiting one of my past projects, I attempted to build the docker image I once prepared but encountered a failure:

$ docker build -t fortune-api:0.4.2 \
'https://github.com/lbacik/fortune-api.git#v0.4.2'
...
6.520 Reading state information...
6.523 E: Unable to locate package fortunes-off
6.523 E: Unable to locate package fortunes-fr
...
ERROR: failed to solve:
process "/bin/sh -c apt-get -y update && apt-get -y install ..."
did not complete successfully: exit code: 100

It had definitely worked in the past – the previously created image is still functional: https://fortune-api.luka.sh/.

Why does it fail now? 🤔

A glimpse at Dockerfile (v0.4.2):

FROM python:3.9

ENV FORTUNES=/usr/share/games/fortunes
ENV CORS=yes

RUN apt-get -y update && apt-get -y install \
fortunes \
fortunes-off \
fortune-anarchism \
fortunes-bg \
fortunes-bofh-excuses \
fortunes-br \
fortunes-cs \
fortunes-de \
fortunes-debian-hints \
fortunes-es \
fortunes-es-off \
fortunes-fr \
fortunes-ga \
fortunes-it \
fortunes-it-off \
fortunes-mario \
fortunes-pl \
fortunes-ru \
fortunes-zh

COPY . /opt/project
WORKDIR /opt/project

RUN pip install -r requirements.txt

EXPOSE 8080

CMD uwsgi --ini config.ini

The problem is the Debian distro version, which the python:3.9 image has been built on. Dockerhub shows now:

When I created the Dockerfile and built it previously, the python:3.9 image used Debian’s “Bullseye” version (debian:11).

The latest version of Debian was released on June 10, 2023. Unfortunately, it caused my Dockerfile build to fail due to package changes.

Certainly, there is a straightforward solution to my problem. I can specify the image version more precisely as python:3.9-bullseye. This should work, but it doesn’t seem too elegant. My application requires data — but the source of this data is not essential (for the app) and can change!

Take a look:

FROM debian:11 as base

# hadolint ignore=DL3008,DL3015
RUN apt-get -y update && apt-get -y install \
fortunes \
fortunes-off \
fortune-anarchism \
fortunes-bg \
fortunes-bofh-excuses \
fortunes-br \
fortunes-cs \
fortunes-de \
fortunes-debian-hints \
fortunes-es \
fortunes-es-off \
fortunes-fr \
fortunes-ga \
fortunes-it \
fortunes-it-off \
fortunes-mario \
fortunes-pl \
fortunes-ru \
fortunes-zh

FROM python:3.9

ENV FORTUNES=/usr/share/games/fortunes
ENV CORS=yes

COPY --from=base /usr/share/games/fortunes /usr/share/games/fortunes

COPY . /opt/project
WORKDIR /opt/project

# hadolint ignore=DL3042
RUN pip install -r requirements.txt

EXPOSE 8080

CMD ["uwsgi", "--ini", "config.ini"]

A multi-stage build fits here great, doesn’t it?!

And for Debian 12:

FROM debian:12 as base

# hadolint ignore=DL3008,DL3015
RUN apt-get -y update && apt-get -y install \
fortune-anarchism \
fortune-mod \
fortunes-bg \
fortunes-bofh-excuses \
fortunes-br \
fortunes-cs \
fortunes-de \
fortunes-debian-hints \
fortunes-eo \
fortunes-es-off \
fortunes-es \
fortunes-ga \
fortunes-it-off \
fortunes-it \
fortunes-mario \
fortunes-min \
fortunes-pl \
fortunes-ru \
fortunes-zh \
fortunes

FROM python:3.9

ENV FORTUNES=/usr/share/games/fortunes
ENV CORS=yes

COPY --from=base /usr/share/games/fortunes /usr/share/games/fortunes

COPY . /opt/project
WORKDIR /opt/project

# hadolint ignore=DL3042
RUN pip install -r requirements.txt

EXPOSE 8080

CMD ["uwsgi", "--ini", "config.ini"]
  1. The app part doesn’t change
  2. There is a separate step to prepare the required data (this step can be easily aligned to the appropriate source)

Isn’t it much cleaner?

Sign up to discover human stories that deepen your understanding of the world.

No responses yet

Write a response