Creating a Docker Image for a FastAPI Application

PUBLISHED ON NOV 16, 2020 — DOCKER, FASTAPI, PYTHON

In this post I’ll quickly cover how to create a Docker container for a FastAPI project.
This is something I did on my stream and thought might be useful to others. The code is available on my GitHub repo.

First, there is a very easy way to do this - you can use the provided image that’s maintained by Sebastián Ramírez (the creator of FastAPI).
How to do that is covered in the official docs for FastAPI, or you can follow thee docs on the Docker Hub repo

I prefer to have slightly more control over my image and how to do things, and I like to learn how to do things myself, so instead I decided to create my own Dockerfile from scratch.
The other advantage to this is that I get to choose when to update things - for example, at the time of writing this there isn’t a Python 3.9 version of the image, which is the version of Python I’m using for my project.

Fortunately, creating the image isn’t too difficult.
For the purposes of this example, I’ve got a src folder for my project, that unsurprisingly contains my source code. Alongside the src folder, I have my requirements.txt file, and my Dockerfile.
In src/main.py I’ve got a single endpoint to simply return ‘Hello World’ - basic, but all that’s needed for this example.
Now for the bit we’re interested in, the Dockerfile itself:

FROM python:3.9-slim

COPY ./src /app/src
COPY ./requirements.txt /app

WORKDIR /app

RUN pip3 install -r requirements.txt

EXPOSE 8000

CMD ["uvicorn", "src.main:app", "--host=0.0.0.0", "--reload"]

Let’s go through this bit by bit.
The first line indicates our base image to build on - I’m using the Python 3.9 image as an easy way of making sure my dependencies are there.
The next two lines are copying over my source files and my requirements.txt file so that the packages can be installed.
We then set the working directory for the image, which sets the directory commands will be run in on the container, which is necessary for installing the dependencies and starting the app.
Now we install the packages required for the app using the requirements.txt file.
And then we’re exposing the port 8000 - this is the default port when running the API. If you’re using a different port for your app, you’ll want to change this.
Finally, we have the command to run the API using uvicorn. The thing to note here is the --hote=0.0.0.0 parameter - this makes the container reachable on localhost when running the container.

To build the image we can run the command docker build -t fastapi-demo . - change the tag for a more appropriate name for you.
And once built, we can run the container with the command docker run -p 8000:8000 -v "${PWD}/src:/app/src. Here we’re binding port 8000 on the container, to port 8000 on our host machine. We’re also adding a volume mount for our code - this will allow you to make any code changes locally without having to re-build and re-run the image.

And there you go, FastAPI…. API…. running in a Docker container, and hot reloading code changes to boot.

Now, if you’ve run this and tried to close the container, you may have noticed a problem.
For me, stopping the container with ctrl + c doesn’t work - I have to run docker kill <image id>. Which is annoying.
The reason for this lies with uvicorn and how it handles signals from our terminal. The issue was addressed in this PR: https://github.com/encode/uvicorn/pull/620
Unfortunately, this PR introduced a problem, and was reverted in this PR: https://github.com/encode/uvicorn/pull/756
So as it stands, this problem hasn’t been resolved.
The only workaround here is to not use the --reload argument in the command in the Dockerfile. If you want the hot reloading, you’ll have to get used to docker kill until this issue is resolved.

Hopefully this post was useful to someone.
If you have any questions feel free to reach out to me on Twitter or drop into the Twitch stream.