Anatomy of a Multi-Stage Docker BuildWed 19 July 2017 by Moshe Zadka
The Flask application itself is the smallest demo app, straight from any number of Flask tutorials:
# src/msbdemo/wsgi.py from flask import Flask app = Flask("msbdemo") @app.route("/") def hello(): return "If you are seeing this, the multi-stage build succeeded"
is the minimal one from any number of Python packaging tutorials:
import setuptools setuptools.setup( name='msbdemo', version='0.0.1', url='https://github.com/moshez/msbdemo', author='Moshe Zadka', email@example.com', packages=setuptools.find_packages(), install_requires=['flask'], )
The interesting stuff is in the
It is interesting enough that we will go through it line by line:
We start from a "fat" Python docker image -- one with the Python headers installed, and the ability to compile extensions.
RUN virtualenv /buildenv
We create a custom virtual environment for the build process.
RUN /buildenv/bin/pip install pex wheel
We install the build tools --
in this case,
wheel, which will let us build wheels,
pex, which will let us build single file executables.
RUN mkdir /wheels
We create a custom directory to put all of our wheels. Note that we will not install those wheels in this docker image.
COPY src /src
We copy our minimal Flask-based application's source code into the docker image.
RUN /buildenv/bin/pip wheel --no-binary :all: \ twisted /src \ --wheel-dir /wheels
RUN /buildenv/bin/pex --find-links /wheels --no-index \ twisted msbdemo -o /mnt/src/twist.pex -m twisted
We build the
togther with any recursive dependencies,
into a Pex file -- a single file executable.
This is where the magic happens.
FROM line starts a new docker image build.
The previous images are available --
but only inside this
for copying files from.
Luckily, we have a file ready to copy:
the output of the Pex build process.
COPY --from=0 /mnt/src/twist.pex /root
--from=0 indicates copying from a previously built image,
rather than the so-called "build context".
In theory, any number of builds can take place in one
While only the last one will actually result in a permanent image,
the others are all available as targets for
In practice, two stages are usually enough.
ENTRYPOINT ["/root/twist.pex", "web", "--wsgi", "msbdemo.wsgi.app", \ "--port", "tcp:80"]
Finally, we use Twisted as our WSGI container.
Since we bound the Pex file to the
-m twisted package execution,
all we need to is run the
ask it to run a
and give it the logical (module) path to our WSGI app.
Using Docker multi-stage builds has allowed us to create a Docker container for production with:
- A smaller footprint (using the "slim" image as base)
- Few layers (only adding two layers to the base slim image)
The biggest benefit is that it let us do so with one Dockerfile, with no extra machinery.