A complete guide to building a Docker Image serving a Machine learning system in Production
A complete step-by-step guide for building a Docker image (GPU or CPU) along with explaining all best practices that should be followed which will be

Search for a command to run...
A complete step-by-step guide for building a Docker image (GPU or CPU) along with explaining all best practices that should be followed which will be

No comments yet. Be the first to comment.
Developing a good ML/AI based product is a hell lot more than just a state-of-the-art model. This is my attempt to help you out & look beyond the models because it's just an idea that all you need 💡
Models based on Transformers are the current sensation of the world of NLP. Hugging Face’s Transformers library provides all SOTA models (like BERT, GPT2, RoBERTa, etc) to be used with TF 2.0 and this blog aims to show its interface and APIs 0. Disc...
Bringing in a fresh perspective on how you can turn your traditional streaming pipeline to perform like batch processing with endless possibilities

Uncover why SQL may not be the best choice for ETL pipelines in data applications and learn about common hurdles.

Turning Data Pipeline Failures into Success Stories: A Memory Optimization Case Study with Polars Library & Data Engineering best practices.

Practical techniques backed by benchmarks for working with Databases effectively in Python

Guide to Efficiently Streamlining Your Databricks Environment Setup

Building a Docker image is generally considered trivial compared to developing other components of a ML system like data pipeline, model training, serving infra, etc. But an inefficient, bulky docker image can greatly reduce performance and can even bring down the serving infra.
This blog aims to focus on building an ideal Docker image and not on its concept or benefits. I am assuming you have basic knowledge of a few topics wrt to Docker:

There quite a few very good source for general best-practice like the official docker guide, but I would like to keep this short and relevant to the ML system based project
eg:
RUN apt update && \
apt install --no-install-recommends -y build-essential gcc curl ca-certificates python3 && \
apt clean && rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir --user -r /req.txtMost of the time a ML system will be based on Python, so it critical to build any Python-based Docker image efficiently. Let us go through it.
FROM python:3.8-slim
RUN apt update && \
apt install --no-install-recommends -y build-essential gcc && \
apt clean && rm -rf /var/lib/apt/lists/*
COPY ./req.txt /req.txt
COPY ./src /src
RUN pip3 install --no-cache-dir -r /req.txt
CMD ['python3', '/src/app.py']
EXPOSE 8080
For demo purpose, I am using the following packages:

After running the docker build command the size of the docker image was 1.64 gb.

# Stage 1: Builder/Compiler
FROM python:3.7-slim as builder
RUN apt update && \
apt install --no-install-recommends -y build-essential gcc
COPY req.txt /req.txt
RUN pip install --no-cache-dir --user -r /req.txt
# Stage 2: Runtime
FROM debian:buster-slim
RUN apt update && \
apt install --no-install-recommends -y build-essential python3 && \
apt clean && rm -rf /var/lib/apt/lists/*
COPY --from=builder /root/.local/lib/python3.7/site-packages /usr/local/lib/python3.7/dist-packages
COPY ./src /src
CMD ['python3', '/src/app.py']
EXPOSE 8080

Comparing them, the multi-stage docker image size is 1.61 gb and the single-stage is 1.64 gb. It's an improvement (even it seems small though), a lot of things are going here, lets us try to understand in a nutshell.
COPY --from=<stage 1> stage1/src stage2/destinationBut as we can see from the screenshot, we are not seeing much significant improvement. We will certainly see a huge improvement in other languages, but python has some tricks in its sleeves,
I would highly insist you to read this article from Real Python explaining what are wheels in python.

From the req.txt that I am using for the demo, only the above packages are not wheel format & also they are already very small in size. But if some packages are not pre-compiled wheel and large in size will end up wasting a lot of disk size
Building a CPU based Docker image is not complex, but not the same case with building a GPU based docker. If not build appropriately, it can end up in humongous size. I will focus on the practical and implementation part and not cover its theory part (as I think it is out of scope for this article).
The most critical part is to select the correct version/tag of CUDA, cuDNN for nvidia docker image and tensorflow/pytorch wrt to it. So that the ML system can utilize underlying gpu hardware. Trust me this can be really frustrating task, so I have some rule of thumb:

We are only interested in base, runtime and not in devel (as we are targeting prod environment). How to select an exact specific tag? I’ll answer it in the following sub-part.
Selecting tag: The rule of thumb which I follow is:
FROM nvidia/cuda:10.1-cudnn7-runtime
RUN apt update && \
apt install --no-install-recommends -y build-essential software-properties-common && \
add-apt-repository -y ppa:deadsnakes/ppa && \
apt install --no-install-recommends -y python3.8 python3-pip python3-setuptools python3-distutils && \
apt clean && rm -rf /var/lib/apt/lists/*
COPY req.txt /req.txt
COPY ./src /src
RUN python3.8 -m pip install --upgrade pip && \
python3.8 -m pip install --no-cache-dir -r /req.txt
CMD ['python3', '/src/app.py']
EXPOSE 8080
Note: As you can the Docker image from nvidia is based on ubuntu 18.04, I have to make a little additional adjustment to install tensorflow=2.2.0.


# Stage 1: Builder/Compiler
FROM python:3.8-slim as builder
RUN apt update && \
apt install --no-install-recommends -y build-essential gcc
COPY req.txt /req.txt
RUN pip install --no-cache-dir --user -r /req.txt
# Stage 2: Runtime
FROM nvidia/cuda:10.1-cudnn7-runtime
RUN apt update && \
apt install --no-install-recommends -y build-essential software-properties-common && \
add-apt-repository -y ppa:deadsnakes/ppa && \
apt install --no-install-recommends -y python3.8 python3-distutils && \
update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 1 && \
update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 2 && \
apt clean && rm -rf /var/lib/apt/lists/*
COPY --from=builder /root/.local/lib/python3.8/site-packages /usr/local/lib/python3.8/dist-packages
COPY ./src /src
CMD ['python3', '/src/app.py']
EXPOSE 8080
Note: To make python 3.8 as default I have added some additional code, if this is not the case for you then you can avoid this hassle.


The primary goal always must be minimal docker image size, since any docker image build for ML system will be always heavy. We should always follow all best practice especially Multi-stage build and versioning of packages. Last but also most important for gpu based images is to test the configuration on the dev environment.
Note: I had originally published this blog at towardsdatascience.com found here