Nyior Clement I'm an all around software engineer who codes, writes, and sometimes designs. If you want to talk Python, I'm your guy. I own this poky corner of the interwebs xD.

Using FastAPI inside Docker containers

6 min read 1683

Fast API Docker Containers

FastAPI is a web framework for building APIs with Python ≥v3.6 that is based on standard Python-type hints. What makes FastAPI stand out is its focus on modern Python, high performance, and ease of use. But, you might be wondering how Docker containers come into the FastAPI conversation.

Imagine that you’re building a REST API and you need to use PostgreSQL, Redis, Celery, RabbitMQ, and a bunch of other dependencies. The first problem you’d run into is configuring all those dependencies on your machine. This setup process could be a hassle, however, there is another, more pressing problem.

What if you’re developing on Linux, but your colleague develops on Windows? You have to keep in mind that some dependencies that work well on Linux don’t work well on Windows. And even if you somehow manage to get past the development phase, what if your development environment isn’t consistent with your deployment environment? All these problems sum up to one thing, portability.

To make your project more portable, you could develop in an isolated environment that has the project code and all the dependencies installed, which is exactly what Docker containers does.

Docker isn’t exclusive to FastAPI; we can use Docker to containerize most projects regardless of what languages or frameworks are used. In this article, we’ll learn how to containerize a FastAPI application with Docker.

Prerequisites

  • Familiarity with the basics of Python and FastAPI
  • Familiarity with the basics of Docker
  • Python ≥v3.7
  • Python Pip, the package manager
  • Docker installed on your machine

Table of contents

Setting up our development environment

Let’s create a rudimentary FastAPI application with one endpoint that returns a list of users.

To create a virtual environment, run python3 -m venv env-name on Unix and macOS or python -m venv env-name on Windows. Replace env-name with the name you chose for your virtual environment.

To activate the virtual environment, run source env-name/bin/activate on Unix and macOS or .\\env-name\\Scripts\\activate on Windows.

In the directory where you want to start your project, run mkdir demo_app, which will create a new folder called demo_app in that directory:

  • Run cd demo_app
  • Run pip install fastapi[all]
  • Run pip freeze > requirements.txt to create a requirements file in the demo_app folder with all the installed dependencies

Adding the Python code

Launch the demo_app folder in the IDE of your choice. Create a file called demo_app/\[main.py\](<http://main.py&gt;) and add the snippet below into the main.py file:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello World"}

@app.get("/users")
async def users():
    users = [
        {
            "name": "Mars Kule",
            "age": 25,
            "city": "Lagos, Nigeria"
        },

        {
            "name": "Mercury Lume",
            "age": 23,
            "city": "Abuja, Nigeria"
        },

         {
            "name": "Jupiter Dume",
            "age": 30,
            "city": "Kaduna, Nigeria"
        }
    ]

    return users

We created two endpoints, one returns “Hello, World!” and the second, /users, returns a dummy list of users. Next, run uvicorn main:app --reload to start your FastAPI application.

Point your browser to [http://127.0.0.1:8000](<http://127.0.0.1:8000/&gt;)/docs. You should see the two endpoints we just added documented:

Fast API Default Endpoints

To test the endpoint, point your browser to \[http://127.0.0.1:8000\](<http://127.0.0.1:8000/&gt;)/users. It should return the dummy list of users, as shown in the image below:

Test Fastapi Endpoint Dummy User List

Now, we have a basic FastAPI application, but to improve the development experience, we can eliminate the need to create a virtual environment and install the project dependencies manually. This optimization would handle what dependencies to install, where to install them, and how to install them when porting our project to other platforms. Let’s learn how to do this with Docker.

Dockerizing our FastAPI application

By running the project inside a Docker container, we can eliminate the need to create a virtual environment and having to install project dependencies manually.

Docker container

You can think of a Docker container as a small computer that runs on another computer. Essentially, a Docker container is just an isolated environment on some machine that contains a project’s code and its dependencies.

When we containerize or dockerize our FastAPI application, we are essentially creating a lightweight virtual box with our project’s dependencies installed and our FastAPI code configured to run. As a result, anyone with our virtual box could run our application without having to deal with the low-level project configuration logistics.



Most importantly, we could simply upload this virtual-box to our staging or production server to make our application go live without having to add a lot of configuration.

Docker image

In our example, what we share with other people or deploy to our servers is not the container or virtual-box itself, but the manual for creating the container. You probably already know this manual as the Docker image.

A Docker image contains step-by-step instructions for building a container. Containers are spun from images during the build step. But, to create a Docker image, we first need to write a Docker file.

To containerize our FastAPI application, we need to follow three steps:

  1. Write a Dockerfile
  2. Build a Docker image from our Dockerfile
  3. Spin up a container from the Docker image we built

Let’s explore each step further.

Writing our Dockerfile

Create a demo_app/Dockerfile file and add the code snippet below:

FROM python:3-slim-buster

RUN mkdir /code

WORKDIR /code

COPY requirements.txt .

RUN pip install -r requirements.txt

COPY . .

CMD ["uvicorn", "main:app", "--host=0.0.0.0", "--port=80"]

We populated our Dockerfile with a set of instructions that the Docker daemon would follow chronologically to build our image when issued the command.

When you install Docker, it automatically installs the Docker client, which accepts Docker commands in your terminal and the Docker daemon. Think of the Docker daemon as Docker’s backend, the main entity that processes commands received by the Docker client.

Let’s make sense of what each command in our Dockerfile above means. The FROM instruction sets the official Python image as the base. It instructs the Docker daemon to build our image on top of an existing image. Docker adopts this layered approach to enhance reusability.

RUN mkdir /code creates a code directory in the image when it is built and eventually the container when it is created.


More great articles from LogRocket:


The WORKDIR instruction sets the default working directory to the newly created /code directory. This working directory will be applicable to any subsequent COPY, ADD, RUN, and CMD instructions.

The first COPY instruction adds the requirements.txt file to the current working directory. The RUN instruction executes the pip install -r requirements.txt command. This command would install the dependencies listed in the requirements file in our container.

The second COPY instruction copies the rest of the content from the current directory . of the host filesystem to the working directory . inside the image and eventually the container. Lastly, the CMD instruction sets the command for running our application’s server published on port 8080.

Building the Docker image from our Dockerfile

Go to your demo_app directory in your terminal and then run the following command:

docker image build --tag demo-app-image .

To build our image, the Docker daemon needs a few pieces of information. For one, it needs the name of the Dockerfile to use. If this isn’t passed, Docker looks for a file named Dockerfile in the working directory. If you name your file anything other than Dockerfile, then you must pass the name of the file using the --file option:

docker image build --file custom-docker-file-name --tag demo-app-image

Docker also needs the build context. The build context is the directory that’s accessible to Docker during the build process. In our case, we specified the current working directory as the build context with ..

If the image build was successful, you should get an output similar to the image below:

Image Build Successful Output

At this point, you should have an image called demo-app-image on your machine. You can run the docker image ls command to see a list of all the images you’ve created or pulled to your local machine from the registry.

Running the container from our Docker image

Run the command below to spin up a container from our demo-app-image:

docker container run --publish 80:80 --name demo-app-container demo-app-image

The --name option creates a container with the name demo-app-container that is based on the demo-app-image. The --publish option forwards requests coming through port 8080 on our local machine to port 8080 in our container.

If the command works, you should get the following output in your terminal:

Command Works Output

You can point your browser to [http://localhost:80/docs](http://localhost/docs) to visit the documentation page. You can also test the /users endpoint. That’s it!

Next steps

Docker containers make our projects more portable. Remember that what we share with other people or push to our deployment environment isn’t the container itself, but the image. Usually, we share an image by publishing it to a public registry like the Docker Hub.

However, it’s not always the case that we’d want our image to be publicly available. In that case, we could push our Dockerfile along with the project code and have other members of the team build the image and run the container on their end.

Conclusion

In this article, we’ve learned how to simplify locally setting up a FastAPI project or deploying to a staging or production environment with Docker containers.

Fundamentally, a Docker container creates a virtual-box-like isolated environment that bundles our application code with its dependencies. As a result, it’s easier to deploy our application anywhere without having to worry about platform-specific inconsistencies.

To containerize a FastAPI application or just any application with Docker, first, we need to add a Dockerfile to the project, build an image from the Dockerfile, and run a container from the image. While Docker is the most popular containerization technology out there, it’s not the only one.

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Nyior Clement I'm an all around software engineer who codes, writes, and sometimes designs. If you want to talk Python, I'm your guy. I own this poky corner of the interwebs xD.

Leave a Reply