A prediction API is not fully deployable merely because it runs inside the development environment. The operating system, Python runtime, dependencies, application code, and model artifact must reach the target environment together. A container image packages those components into one versioned unit.
This chapter containerizes the FastAPI service developed in Serving Predictions with FastAPI and tested in Validation, Errors, and API Testing. The objective is not simply to make Docker run. It is to create a small, inspectable image whose behaviour can be verified before it is released.
Learning objectives
By the end of this chapter, you should be able to:
explain the difference between an image and a running container;
define a minimal Docker build context;
write a production-oriented Dockerfile for a FastAPI service;
build, run, inspect, and stop the container safely;
verify the container through the same HTTP contract used outside Docker; and
identify common failures involving ports, paths, dependencies, and model artifacts.
From source code to a running container
The container workflow has two distinct phases. docker build creates an immutable image from the project files and instructions. docker run starts an isolated process from that image.
Code
flowchart TD A["Application, model, and dependencies"] --> B["Docker build context"] B --> C["Versioned image"] C --> D["Running container"] D --> E["Health and prediction checks"]
flowchart TD
A["Application, model, and dependencies"] --> B["Docker build context"]
B --> C["Versioned image"]
C --> D["Running container"]
D --> E["Health and prediction checks"]
An image is a packaged template. A container is a running instance of that template. Several containers can be started from the same image, but runtime changes inside one container do not update the image.
Prerequisites
Complete the API implementation and tests before containerizing it. At minimum, the repository should contain:
The examples assume that the FastAPI object is named app in app/main.py, giving the import string app.main:app. If your module differs, change that string consistently in the Dockerfile and local commands.
Confirm that Docker is available:
docker--versiondocker info
docker --version confirms that the client is installed. docker info also checks whether the Docker engine is running.
Define the runtime dependencies
The container must install the packages required at runtime. A focused deployment file avoids pulling notebook and development tools into the image. For a small guide project, the existing requirements.txt may be used if it already contains the following runtime dependencies:
Pinning exact versions in requirements-lock.txt improves reproducibility. When a tested lock file exists, substitute it for requirements.txt in the Dockerfile. Do not generate a lock file from an unrelated virtual environment.
Do not exclude models/ when the application loads its model from that directory. Never bake credentials or production secrets into the image. Supply runtime configuration through the deployment platform or environment variables.
Build the image
From the repository root, run:
docker build --tag cdi-model-api:07 .
The final period selects the current directory as the build context. The explicit tag identifies both the application and the chapter-stage version.
Inspect the result:
docker image ls cdi-model-apidocker image inspect cdi-model-api:07
A successful build proves that the image instructions completed. It does not prove that the service starts correctly or returns valid predictions.
Run the container
Start the service in the background:
docker run \--detach\--name cdi-model-api-07 \--publish 8000:8000 \ cdi-model-api:07
The mapping 8000:8000 means host-port:container-port. The API should now be available at http://127.0.0.1:8000.
Then run the Chapter 06 contract checks against the containerized service:
bash scripts/bash/06-test-api.sh
The same tests should pass whether Uvicorn runs from the virtual environment or inside a container. That equivalence is important: containerization should change packaging, not the API contract.
Automate the smoke test
The companion script builds the image, starts a temporary container, waits for the health endpoint, runs the Chapter 06 API test script when it is available, and removes the container on exit:
bash scripts/bash/07-build-and-test-container.sh
Optional environment variables allow the defaults to be changed without editing the script:
The script deliberately removes only the container it creates. It does not delete the resulting image, so the verified artifact remains available for inspection or later use.
Add an image health check
An HTTP health endpoint is useful only when the runtime checks it. Python is already present in the image, so a health check can avoid adding another system package. Add the following instruction before CMD:
The endpoint should confirm that the process can serve requests. If prediction readiness depends on a loaded model, the health logic should also verify that the artifact was loaded successfully.
Inspect the packaged application
Use read-only inspection when debugging the packaged files:
These checks answer two frequent questions: whether the Python module can be imported and whether the model artifact is present at the path expected by the application.
Stop and remove the container
When a manually started container is no longer needed:
Stopping ends the process; removing deletes that container instance. The image remains available. To remove the image later, first confirm that it is no longer needed:
docker image rm cdi-model-api:07
Troubleshooting
The port is already in use
Map a different host port while leaving the container port unchanged:
docker run --rm--publish 8001:8000 cdi-model-api:07
The service is then available at http://127.0.0.1:8001.
Common causes include an invalid import string, a missing package, or a model-loading exception during application startup.
The model file cannot be found
Relative paths depend on the container working directory. Resolve model paths from the application module or from the known /app working directory, and confirm that .dockerignore does not exclude the artifact.
The API works locally but not through Docker
Confirm that Uvicorn binds to 0.0.0.0, the correct port is published, and the request is sent to the host-side port. Binding to 127.0.0.1 inside the container makes the service unreachable from the host.
The image is unexpectedly large
Inspect its layers:
docker history cdi-model-api:07
Use a slim base image, exclude development files, avoid copying the entire repository, and keep build-only tools out of the final runtime image.
Reproducibility and security checklist
Before treating the image as a deployment candidate, confirm that:
the API and model tests pass before the image is built;
dependency versions come from the tested project environment;
the model artifact is versioned or traceable to its training run;
the build context excludes credentials, caches, reports, and local environments;
the application runs as a non-root user;
the image has a unique, meaningful tag rather than relying only on latest;
the container passes health and prediction checks; and
image metadata, source revision, and evaluation evidence can be connected.
Containerization improves environmental consistency, but it does not validate the model, secure the surrounding platform, or monitor prediction quality. Those responsibilities remain part of the wider deployment system.
Chapter summary
A deployment-ready container is more than a successful docker build. It is a reproducible package containing the required runtime, dependencies, application, and model artifact. Its service must start under container constraints and satisfy the same validated API contract established before packaging.
The workflow is therefore:
test the application outside the container;
define a minimal and safe build context;
build a versioned image;
run the image with an explicit port mapping;
verify health and prediction behaviour; and
inspect and retain evidence for the exact image tested.
The next chapter can build on this verified image by adding configuration, release, and operational deployment practices.