Dockerizing an ML Application
“It works on my machine” is not a deployment strategy. Your model was trained with a specific Python version, a specific scikit-learn version, maybe a specific BLAS library underneath NumPy — and any mismatch on the server can silently change predictions or crash the app outright. Docker solves this by packaging your code, its dependencies, and the runtime it needs into one image that runs identically everywhere: your laptop, a teammate’s laptop, or a cloud VM.
Why Docker helps
Docker packages:
- code
- dependencies
- runtime environment
So it runs the same everywhere.
flowchart LR C[Code] --> I[Docker image] D[Dependencies] --> I R[Runtime config] --> I I --> S[Run anywhere]
A simple Dockerfile (FastAPI)
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]Building and running the container
# build the image and tag it "ml-api"
docker build -t ml-api .
# run it, mapping container port 8000 to host port 8000
docker run -p 8000:8000 ml-api
# in another terminal, confirm it responds
curl http://127.0.0.1:8000/predict -X POST \
-H "Content-Type: application/json" \
-d ‘{"age": 35, "income": 50000, "city": "Pune", "plan": "Pro"}’# build the image and tag it "ml-api"
docker build -t ml-api .
# run it, mapping container port 8000 to host port 8000
docker run -p 8000:8000 ml-api
# in another terminal, confirm it responds
curl http://127.0.0.1:8000/predict -X POST \
-H "Content-Type: application/json" \
-d ‘{"age": 35, "income": 50000, "city": "Pune", "plan": "Pro"}’Keep the image lean with .dockerignore
Without one, COPY . .COPY . . also copies your .venv.venv, .git.git history, notebooks,
and any large .csv.csv/.joblib.joblib files sitting in the repo — bloating the image
and slowing every rebuild. Add a .dockerignore.dockerignore right next to the Dockerfile:
.git
.venv
__pycache__
*.ipynb
*.csv
tests/.git
.venv
__pycache__
*.ipynb
*.csv
tests/Mount the model as a volume instead of baking it in
Géron’s TF Serving walkthrough mounts the model directory into the container at run time rather than copying it in at build time:
docker run -it --rm -p 8500:8500 -p 8501:8501 \
-v "$ML_PATH/my_mnist_model:/models/my_mnist_model" \
-e MODEL_NAME=my_mnist_model \
tensorflow/servingdocker run -it --rm -p 8500:8500 -p 8501:8501 \
-v "$ML_PATH/my_mnist_model:/models/my_mnist_model" \
-e MODEL_NAME=my_mnist_model \
tensorflow/servingThe same trick works for a Flask/FastAPI image. Instead of COPY model.joblib .COPY model.joblib .
baked into the image, mount the artifact from the host (or a shared volume):
docker run -p 8000:8000 \
-v "$(pwd)/models:/app/models" \
-e MODEL_PATH=/app/models/model_v2.joblib \
ml-apidocker run -p 8000:8000 \
-v "$(pwd)/models:/app/models" \
-e MODEL_PATH=/app/models/model_v2.joblib \
ml-apiNow shipping model_v3.joblibmodel_v3.joblib and pointing MODEL_PATHMODEL_PATH at it is a config
change, not a new image build — the same “rolling back is as simple as
removing the directory” idea Géron describes for TF Serving’s versioned
model folders applies just as well to a hand-rolled API image.
Let Docker check the container’s own health
A HEALTHCHECKHEALTHCHECK instruction lets Docker (and orchestrators built on top of
it) know when a running container has actually finished starting up and is
serving traffic — not just that the process launched:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8000/health || exit 1
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8000/health || exit 1
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]docker psdocker ps will now show (healthy)(healthy) or (unhealthy)(unhealthy) next to the container,
and tools like Kubernetes or a load balancer can use that status to decide
whether to keep sending it traffic.
Running the API and a Streamlit UI together with Compose
Most real setups aren’t a single container — an API and a demo UI often run
side by side, sharing the same model artifact. docker-compose.ymldocker-compose.yml lets you
describe both services and start them with one command:
services:
api:
build: .
ports:
- "8000:8000"
volumes:
- ./models:/app/models
ui:
build: ./streamlit_app
ports:
- "8501:8501"
depends_on:
- apiservices:
api:
build: .
ports:
- "8000:8000"
volumes:
- ./models:/app/models
ui:
build: ./streamlit_app
ports:
- "8501:8501"
depends_on:
- apidocker compose up --build
# API -> http://localhost:8000
# UI -> http://localhost:8501docker compose up --build
# API -> http://localhost:8000
# UI -> http://localhost:8501From one container to many
A single container is fine for a demo, but real traffic needs more than one replica behind a load balancer, plus a way to roll out new image versions without downtime. That’s the job of an orchestrator like Kubernetes — Docker gives you the portable unit, Kubernetes (or a managed platform) gives you the scaling and rollout logic.
flowchart LR Img["Docker image ml-api:v1"] --> C1["Container replica 1"] Img --> C2["Container replica 2"] Img --> C3["Container replica 3"] LB["Load balancer"] --> C1 LB --> C2 LB --> C3 Client["Client requests"] --> LB
tips
- pin versions in
requirements.txtrequirements.txt - keep images small (
slimslimbase) - don’t bake secrets into images
Mini-checkpoint
Why is Docker useful for ML specifically?
- ML dependencies are often heavy and version-sensitive.
🧪 Try It Yourself
Exercise 1 – Build the docker run Command
Exercise 2 – Read the PORT from the Environment
Exercise 3 – Tag an Image with a Version
Next
Continue to Monitoring Model Drift — once your model is packaged and running somewhere, the next job is watching it stay accurate over time.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
