Now that you understand the need for containerization to create consistent environments, let's focus on the core component that defines how your application is packaged: the Dockerfile
. Think of a Dockerfile
as a recipe or a set of instructions that Docker follows to build an image containing your FastAPI application, its dependencies, necessary data files (like your ML model), and the information needed to run it.
A Dockerfile
is simply a text file named Dockerfile
(with no extension) placed in the root directory of your project. Inside this file, you write a series of commands, one per line, that specify the steps for assembling the image.
While there are many possible instructions you can use in a Dockerfile
, a typical setup for a FastAPI application usually involves a few common ones:
FROM
: Specifies the base image to build upon. Every Dockerfile
must start with a FROM
instruction. For Python applications, you'll typically use an official Python image (e.g., python:3.9
). This provides a starting Linux environment with Python and pip pre-installed.WORKDIR
: Sets the working directory for subsequent instructions like RUN
, CMD
, COPY
, and ADD
. If the directory doesn't exist, WORKDIR
will create it. It helps organize your container's filesystem.COPY
: Copies files or directories from your local machine (the build context) into the filesystem of the container image. You'll use this to get your application code, requirements.txt
file, and ML model artifacts into the image.RUN
: Executes commands in a new layer on top of the current image. This is commonly used to install Python dependencies using pip install
. Each RUN
instruction creates a new image layer, which has implications for caching and image size.EXPOSE
: Informs Docker that the container listens on the specified network ports at runtime. This doesn't actually publish the port; it functions as documentation between the person who builds the image and the person who runs the container. For FastAPI applications run with Uvicorn's default settings, this is typically port 8000.CMD
: Provides the default command to execute when a container starts from the image. There can only be one CMD
instruction in a Dockerfile
. If you specify an executable, it should be the first parameter. For running FastAPI, this command usually starts the Uvicorn server.Let's put these instructions together into a practical Dockerfile
for the ML prediction service we've been building. Assume your project structure looks something like this:
.
├── app/
│ ├── main.py
│ ├── models/
│ │ └── your_model.joblib
│ └── ... (other python files)
├── requirements.txt
└── Dockerfile
Here’s a sample Dockerfile
:
# Use an official Python runtime as a parent image
FROM python:3.9-slim
# Set the working directory in the container
WORKDIR /app
# Copy the requirements file into the container at /app
COPY requirements.txt .
# Install any needed packages specified in requirements.txt
# Use --no-cache-dir to reduce image size
# Use --compile to byte-compile python files for faster startup (optional)
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir --compile -r requirements.txt
# Copy the application code and model files into the container at /app
COPY ./app /app/app
COPY ./app/models /app/app/models
# Make port 8000 available to the world outside this container
EXPOSE 8000
# Define environment variable (optional, can be set at runtime too)
ENV MODEL_PATH=/app/app/models/your_model.joblib
# Run main.py when the container launches using Uvicorn
# Listen on 0.0.0.0 to be accessible from outside the container
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Let's break down the example Dockerfile
step-by-step:
FROM python:3.9-slim
: We start with a slim version of the official Python 3.9 image. Using slim
variants often results in smaller image sizes compared to the full image, as they include only the minimal packages needed to run Python.WORKDIR /app
: We set the default directory inside the container to /app
. All subsequent commands will run relative to this path.COPY requirements.txt .
: We copy only the requirements.txt
file from our project directory into the /app
directory inside the container. Note the .
indicating the destination directory (/app
because of WORKDIR
).RUN pip install ...
: This is a critical step. We first upgrade pip
itself and then install all the libraries listed in requirements.txt
.
--no-cache-dir
: This flag tells pip not to store the download cache, which helps keep the image layer smaller.--compile
: This pre-compiles Python source files to bytecode (.pyc
), which can slightly speed up application startup.requirements.txt
and run pip install
before copying the rest of the application code. This leverages Docker's build cache. If requirements.txt
hasn't changed, Docker can reuse the layer created by this RUN
instruction, speeding up subsequent builds even if your application code changes.COPY ./app /app/app
: We copy the contents of our local app
directory (containing main.py
, etc.) into a subdirectory named app
inside the container's /app
directory. The destination path becomes /app/app
.COPY ./app/models /app/app/models
: Similarly, we copy the ML model artifacts from the local app/models
directory into /app/app/models
within the container. You might adjust this depending on where your models are stored relative to your Dockerfile
.EXPOSE 8000
: We document that the application inside the container will listen on port 8000.ENV MODEL_PATH=...
: This sets an environment variable inside the container. Your FastAPI application can then read this environment variable to know where to load the model file from, making the path configurable.CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
: This defines the command that will run when a container is started from this image. It executes the uvicorn
server, telling it to run the app
object found in the app/main.py
module.
--host 0.0.0.0
: This is important. It makes Uvicorn listen on all available network interfaces inside the container, allowing connections from outside the container (when the port is published). Using the default 127.0.0.1
would only allow connections from within the container itself.--port 8000
: Matches the port specified in the EXPOSE
instruction..dockerignore
FileSimilar to .gitignore
, you can create a .dockerignore
file in the same directory as your Dockerfile
. This file lists patterns of files and directories that should be excluded from the build context sent to the Docker daemon. This prevents unnecessarily large build contexts and avoids copying sensitive information or irrelevant files (like virtual environments, .pyc
files, build artifacts, .git
directories) into your image.
A typical .dockerignore
might look like:
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
env/
venv/
.git/
.pytest_cache/
By defining this Dockerfile
, you provide a clear, repeatable set of instructions for packaging your FastAPI ML application. The next step is to use this file to build the Docker image and run it as a container.
© 2025 ApX Machine Learning