Build a dockerized FastAPI application with poetry and gunicorn.
Tags: docker, fastapi, python, poetry
Published: 2022-09-01 07:19:42

## Prerequisites

- Python3
- docker
- docker-compose


## Setup FastAPI application

### Why use Poetry for dependency and virtual environment management instead of pip and it's equilvalent python setups

Many developers loved the experience provided by Yarn and Npm in the NodeJS ecosystem. Poetry gives a similar experience to developers in the Python world.

This tutorial compares the approach used by npm for dependency management with poetry to show some of it's greatest strength and why you should use it.

Similar to `package.json` file in the NodeJS world, poetry uses a file named `pyproject.toml` file to specify the configurations for your project, and generates a lockfile automatically to describe the entire dependency tree.

A `pyproject.toml` file looks like this;


![Screenshot 2022-08-30 at 22.44.58.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1661895935250/AghD4Sgv3.png align="left")

### Setting up poetry on your machine

With python installed in your system, you can easily run
```
pip install poetry
``` 
For python3
```
pip3 install poetry
```

 Successful installation can be confirmed with

```
poetry --version
```

### App dependencies and virtual environment

With poetry installed, we can start a python application by simply running the following command
```
poetry init
``` 
and follow the command line prompt.
This command is similar to `npm init -y` in the NodeJS ecosystem.

 a `pyproject.toml` file should be created where you can add your dependencies (the libraries that you will use during the project).

Activate the virtual environment with the following command
```
poetry shell
```

The last step is to add the dependencies for starting up a FastAPI application.

Adding a core dependency with poetry is simply done with `poetry add [package name`, while adding a dev dependency requires a `--dev` flag.

To begin working on our application, we need fastapi and unicorn as dependencies.

```
poetry add fastapi uvicorn[standard] gunicorn
```

### Starting the application server with uvicorn

Firstly we need to create a new python file called main.py, this file will contain our route and also the app object, which is responsible for instantiating the FastAPI application.

Finally, we can start the application with the command shown below;
```
uvicorn main:app --reload
```

Now your application is running on [http://127.0.0.1:8000](http://127.0.0.1:8000) and the good news is: FastAPI is integrated with Swagger, the documentation is automatically generated and you can check it via [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs)

## Dockerize the FastAPI application

The first step for creating a docker container is to specify what, how and on which OS we need the application to run, and all these configurations reside in a file named as `Dockerfile`.


```
FROM python:3.10 as python-base
RUN mkdir python_tutorial
WORKDIR  /python_tutorial
COPY /pyproject.toml /python_tutorial
RUN pip3 install poetry
RUN poetry config virtualenvs.create false
RUN poetry install 
COPY . .
CMD ["gunicorn", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "main:app", "--bind", "0.0.0.0:8000"]
```
Let’s follow the file, line by line:

Line 1: Represents the base image (in our case its python), I have specified version 3.10 and  you may change it accordingly(eg 3.9, 3.7, ..etc)

Line 2: Lets us create a folder named python_tutorial in our container

Line 3: Sets the created directory as our working directly.(So, all the commands below will be executed w.r.t python_tutorial as our root directory)

Line 4: Copy the file with the requirements to the application directory. Copy only the file needed to install the app dependencies only, and not the rest of the files in the app directly. As this file doesn’t change often, Docker will detect it and use the cache for this step, enabling the cache for the next step too.

Line 5: We install poetry so it can be used for dependency management in our app container

Line 6: We disable virtual environment creation inside the container, as it is only needed on the local machine

Line 7: We install all required libraries within container.

Line 8 : Copy all the files. As this has all the code which is what changes most frequently the Docker cache won’t be used for this or any following steps easily. So, it’s important to put this near the end of the Dockerfile, to optimize the container image build times.

line 9: Exposes our application to use gunicorn for starting the app. setting the worker to 4, and also bind the app port to 8000

Whoffff!!! Fans self! 
![fans-self-sweating.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1661898603950/nppzJa-OG.gif align="left")
I know that's a lot of facts, so let's take a break.😅

The current structure of our directory looks like this;
![Screenshot 2022-08-30 at 23.35.12.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1661899218396/i6TOf9_vQ.png align="left")

To elegantly manage our application docker image, we decide to use docker-compose. With docker-compose, we can configure and start multiple Docker containers connected to our application on the same host with a single command, and Hey! it looks cool🫣


![cool.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1661899536265/dzZauPiXG.gif align="left")

Now, lets configure docker-compose.yml

```
version: "3"
services:
  api:
    build: .
    image: python_tutorial
    ports:
      - "8000:8000"
    volumes:
      - .:/python_tutorial:ro
```
Let's take a look at what's going on in the file;

Line 1: specifies version of docker-compose.

Line 3: We set a service named api.

Line 4 → 5: Specifies the path to Docker file and the tag name respectively.

Line 6: Specifies port mappings <host_port>:<container_port>

Line 7: sets the bind mount, to allow further changes to the application(development - not necessary in production)

Line 8: specifies the env file, if present

and finally run the following command;
```
docker-compose up
```

And if you are having a good day, you will get the following results on your terminal.


[Screenshot 2022-08-31 at 00.09.30.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1661900987252/1JVKz8FCu.png align="left")