In this tutorial we will see how to Deploy FastAPI on Ubuntu. Our FastAPI application does CRUD operations on a PostgreSQL database running on Ubuntu 18.04.5 LTS (Bionic Beaver). We expose FastAPI running on Gunicorn as a reverse proxy using Caddy 2 Web Server.
Table of Contents
- Architectural Overview
- Prerequisites
- Install and Configure PostgreSQL 13 Database on Ubuntu 18.04.5 LTS (Bionic Beaver)
- Deploy the FastAPI CRUD PostgreSQL on Ubuntu
- Run FastAPI on Gunicorn as a Systemd managed Unit Service
- Install Caddy 2 Web Server on Ubuntu 18
- Final Sanity Check and Troubleshooting deployment
- Video Tutorial
- Summary
- Where you want to go from here?
Architectural Overview
Once we deploy FastAPI on Ubuntu, it can be visualized as follows.
As you take a look at the Architectural Overview of FastAPI Deployment, we are targeting a single VM deployment in this tutorial that could be later scaled as per our need. You can follow this tutorial on any Cloud VM. In this case, the FastAPI is deployed on Azure Linux Virtual Machine that carries Ubuntu 18 OS.
Within the Ubuntu VM, we have two systemd services namely caddy.service
and gunicorn.service
up and running. The gunicorn.service
runs the FastAPI application and the caddy.service
exposes the FastAPI application running on Gunicorn as a reverse proxy with the help of uvicorn.workers.UvicornWorker
worker class. In addition to this, our FastAPI communicates to PostgreSQL database server in an asynchronous fashion with the help of databases package that provides simple asyncio
support for PostgreSQL database.
Prerequisites
- Internet Connection to clone GitHub Repository
- A Ubuntu 18.04.5 LTS (Bionic Beaver) Virtual Machine with
- SSH Port 22 enabled
- Port 80 (for HTTP traffic) and Port 443 (for HTTPS traffic) enabled
- Putty to connect to VM
Before we begin, if you are following along with the tutorial, connect to the Ubuntu VM via Putty or cmder. All the shell commands need to be run in the command line terminal in the connected instance of the VM you are actively working on.
The full source code of this tutorial is available on github on fastapi-postgresql-caddy-ubuntu-deploy
branch here. In case you are interested in understanding the development, you can go through this tutorial on Implementing Async REST APIs in FastAPI with PostgreSQL CRUD
To achieve deployment of FastAPI on Ubuntu 18.04.5 LTS (Bionic Beaver), we will perform the activities in the sequence mentioned below
- Install and Configure PostgreSQL Database
- Clone FastAPI Repository from GitHub
- Run FastAPI on Gunicorn as a Systemd Unit Service
- Install Caddy 2 Web Server
- Expose FastAPI application as a reverse proxy via Caddy 2 Web server
Install and Configure PostgreSQL 13 Database on Ubuntu 18.04.5 LTS (Bionic Beaver)
For Ubuntu 18.04.5 LTS (Bionic Beaver) VM, run the script to install PostgreSQL 13.
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - sudo apt update sudo apt install -y postgresql-13
Once you are done installing the PostgreSQL 13, run the below command that
- creates a database named fastapidb and
- creates a user named fastapiuser which prompts you to provide password for this user.
sudo su - postgres -c "createdb fastapidb" sudo su - postgres -c "createuser -P -s -e fastapiuser" # Enter password for new role: secret # Enter it again: secret
Enter and confirm the password. Run the following command to grant all privileges on fastapidb to fastapiuser.
sudo -u postgres psql -c "grant all privileges on database fastapidb to fastapiuser;"
Running the above command should return an output saying GRANT
.
We are done with the database set up. To understand in detail, I recommend you to go through this tutorial for detailed step by step tutorial on Installing PostgreSQL Database on Ubuntu.
Deploy the FastAPI CRUD PostgreSQL on Ubuntu
Now we will clone the FastAPI sample application that does CRUD operations on PostgreSQL database by running the following command.
git clone https://github.com/windson/fastapi.git -b fastapi-postgresql-caddy-ubuntu-deploy cd fastapi
Create Virtual Environment
Run the following command to install python3-venv
package that will facilitate creation of virtual environments for Python applications.
sudo apt install -y python3-venv
Then create a virtual environment by running the following commands. Create virtual environment named env
python3 -m venv env
Activate virtual Environment and Install requirements
We will activate the virtual environment and then install all the dependencies in order for our FastAPI application to run. The list of dependencies are mentioned in a file named requirements.txt
. Running the following commands will facilitate installation of all the modules within the virtual environment that are necessary for our FastAPI application to run.
source env/bin/activate pip install --upgrade pip pip install -r requirements.txt deactivate
Perform Sanity Check
Perform a sanity check by spinning up the FastAPI application on uvicorn
. This is the very first time we are running our app and doing so will create the notes
table based on schema defined in our application in the PostgreSQL database.
source env/bin/activate uvicorn main:app
Output:
INFO: Started server process [7444]
INFO: Waiting for application startup.
INFO: Connected to database postgresql://fastapiuser:********@localhost:5432/fastapidb?sslmode=prefer
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Once you see the application running smooth, you can stop running it by issuing ctrl
+ c
.
Run FastAPI on Gunicorn as a Systemd managed Unit Service
Gunicorn “Green Unicorn” is a Python WSGI (Web Server Gateway Interface) HTTP server. It is a pre-fork worker model, ported from Ruby’s Unicorn project.
FastAPI application runs on ASGI compatible server like Uvicorn and Hypercorn. Gunicorn being a WSGI webserver facilitates running FastAPI with the help of its worker class uvicorn.workers.UvicornWorker
.
Let’s create a Systemd managed Unit service gunicorn.service
. This could be any name of your choice like fastapi.service
or gunicorn.service
. I will go on with gunicorn.service
because the unit file that we are going to create can not only be used for FastAPI but also be used for Flask or any web framework that supports on Gunicorn Web Server. The only difference lies in the way we spin up the app using the command held by ExecStart
property of the Unit file.
What are Unit Files in Unix?
Unit files are the configuration files managed by the Systemd that hold the behavior of a specific service that we want to deal with. They get added as services and allow various capabilities to start the service when a system reboot happens, or retry restarting the service in case of failure and offer various other capabilities.
We will create a new gunicorn.service unit file by running the following command.
sudo nano /etc/systemd/system/gunicorn.service
With the following content and replace windson
with your username and check the paths are valid for WorkingDirectory
, Environment
and ExecStart
.
# gunicorn.service # For running Gunicorn based application with a config file - TutLinks.com # # Make sure the ExecStart and ExecReload commands are correct # for your installation. # # # Author: Navule Pavan Kumar Rao # This file is referenced from official repo of TutLinks.com # https://github.com/windson/fastapi/blob/fastapi-postgresql-caddy-ubuntu-deploy/gunicorn.service # Subscribe to TutLinks channel on YouTube: http://bit.ly/2Uc0YNk # [Unit] Description=Gunicorn Web Server as Unit Service Systemd - TutLinks.com After=network.target [Service] User=windson Group=windson WorkingDirectory=/home/windson/fastapi Environment="PATH=/home/windson/fastapi/env/bin" ExecStart=/home/windson/fastapi/env/bin/gunicorn --config /home/windson/fastapi/gunicorn.py main:app [Install] WantedBy=multi-user.target
Save and exit out of the nano editor by hitting the keys ctrl + x
and for Answer, type Y
and hit return (enter) key to apply changes.
The path for ExecStart
is /home/windson/fastapi/env/bin/gunicorn
that has the /env
which indicates the name of the virtual environment. If you have given a different name for virtual environment, you can replace env
with the name of your Python virtual environment.
The Gunicorn Unit file uses the Gunicorn binary available in the virtual environment installed for our FastAPI to run. The version of Gunicorn binary is based on the version of Gunicorn mentioned in the requirements.txt file.
We are providing the gunicorn with the location of configuration file that holds various arguments in the form of literals enclosed in a python file named gunicorn.py which is available in our repository.
import multiprocessing import os from dotenv import load_dotenv load_dotenv() name = "gunicorn config for FastAPI - TutLinks.com" accesslog = "/home/windson/fastapi/gunicorn-access.log" errorlog = "/home/windson/fastapi/gunicorn-error.log" bind = "0.0.0.0:8000" worker_class = "uvicorn.workers.UvicornWorker" workers = multiprocessing.cpu_count () * 2 + 1 worker_connections = 1024 backlog = 2048 max_requests = 5120 timeout = 120 keepalive = 2 debug = os.environ.get("debug", "false") == "true" reload = debug preload_app = False daemon = False
The gunicorn.py
will hold various configuration arguments accepted by the gunicorn binary (or executable) such as the ones mentioned below among others.
bind
that holds host and port to run the application,accesslog
that logs the HTTP traffic of the application served by gunicornerrorlog
that logs the errors related to the gunicorn serverworker_class
that holds the valueuvicorn.workers.UvicornWorker
that helps running FastAPI applicationworkers
that denotes the number of workers based on calculation of twice the number of CPUs + 1. Mathematically., number of workers = ( 2 * CPU Count ) + 1
The gunicorn.py
provided as a config for gunicorn binary in unit file offers the flexible configuration for workers
argument. The devops doesn’t have to worry about changing the number of workers every time they deploy it to a VM with different CPUs. It offers the advantage of automatically calculating the available number of CPUs on the machine it is deployed to.
Now we will register the unit file gunicorn.service
with Systemd by executing the following commands.
sudo systemctl daemon-reload sudo systemctl enable gunicorn.service sudo systemctl start gunicorn.service
The systemctl enable
command above will add our gunicorn service to resume running when the VM reboots.
The systemctl start
command will quickly start the gunicorn service and invokes the ExecStart
command.
To check the status of our gunicorn.service at any point of time, run the following command
sudo systemctl status gunicorn.service
The output should show the Active
status of gunicorn.service as active (running)
as shown below.
● gunicorn.service - Gunicorn Web Server as Unit Service Systemd - TutLinks.com
Loaded: loaded (/etc/systemd/system/gunicorn.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2020-10-07 06:04:20 UTC; 8h ago
Main PID: 1102 (gunicorn)
Tasks: 16 (limit: 1057)
CGroup: /system.slice/gunicorn.service
├─1102 /home/windson/fastapi/env/bin/python3 /home/windson/fastapi/env/bin/gunicorn --config /home/windson/fastapi/gunicorn.py main:app
├─3743 /home/windson/fastapi/env/bin/python3 /home/windson/fastapi/env/bin/gunicorn --config /home/windson/fastapi/gunicorn.py main:app
├─3744 /home/windson/fastapi/env/bin/python3 /home/windson/fastapi/env/bin/gunicorn --config /home/windson/fastapi/gunicorn.py main:app
└─3781 /home/windson/fastapi/env/bin/python3 /home/windson/fastapi/env/bin/gunicorn --config /home/windson/fastapi/gunicorn.py main:app
Oct 07 06:04:20 ubu-18 systemd[1]: Started Gunicorn Web Server as Unit Service Systemd - TutLinks.com.
With in the ssh console of the VM, send a curl command that sends POST request along with payload to add a note to our FastAPI application.
curl -X POST "http://localhost:8000/notes/" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ -d "{\"text\":\"Get Groceries from the store\",\"completed\":false}"
You should be seeing the following output that responds with the note that got successfully inserted in the PostgreSQL database via FastAPI application.
{"id":1,"text":"Get Groceries from the store","completed":false}
Rotating Gunicorn accesslog and errorlog files using logrotate
If you are interested in enabling log files for gunicorn service configuration, then ensure to rotate these files. As logrotate is a module available by default in debian systems, perform the following steps to rotate the accesslog
and errorlog
files.
Gunicorn logs may be configured by adding the following to the end of /etc/logrotate.conf
file.
To edit the logroate.conf file type the below command.
sudo nano /etc/logrotate.conf
Copy the below configuration that defines the rotation of gunicorn-access.log and gunicorn-error.log files on a daily basis. Paste it at the bottom of the /etc/logrotate.conf
file. Ensure to change the user windson according to the user you want to.
/home/windson/fastapi/gunicorn-access.log /home/windson/fastapi/gunicorn-error.log {
missingok
daily
create 0700 windson windson
dateext
rotate 4
compress
}
To save and exit out of the /etc/logrotate.conf
file hit ctrl + x
and Answer Y
and hit return.
Run the following command to take these changes effect on logrotate.
sudo logrotate /etc/logrotate.conf
Till now, we accomplished the following setup
- Installing and configuring PostgreSQL server
- Spin up a FastAPI CRUD app that talks to PostgreSQL as a Unit Service managed by Systemd
- Configured log rotate for Gunicorn accesslog and errorlog files using logrotate
Install Caddy 2 Web Server on Ubuntu 18
We will install Caddy 2 web server in order to expose our API to the external world. Caddy 2 is an open source web server build using Go
programming language. It offers range of impressive features such as below to mention a few.
- automatic https for custom domains by default from LetsEncrypt,
- easy configuration from json or Caddyfile,
- Supports HTTP/1.1, HTTP/2, and experimental support for HTTP/3 protocols
Lets install Open Source Caddy 2 Web Server by running the following commands.
echo "deb [trusted=yes] https://apt.fury.io/caddy/ /" | sudo tee -a /etc/apt/sources.list.d/caddy-fury.list sudo apt update sudo apt install -y caddy
Once the installation is done, run the following command to check the version of caddy
caddy version
Output:
v2.2.0 h1:sMUFqTbVIRlmA8NkFnNt9l7s0e+0gw+7GPIrhty905A=
Also check the caddy is running actively as a Systemd service by running the following command.
systemctl status caddy
Output:
● caddy.service - Caddy
Loaded: loaded (/lib/systemd/system/caddy.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2020-10-07 12:57:29 UTC; 1h 34min ago
Docs: https://caddyserver.com/docs/
Main PID: 12183 (caddy)
Tasks: 6 (limit: 1057)
CGroup: /system.slice/caddy.service
└─12183 /usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
Oct 07 12:57:29 ubu-18 caddy[12183]: JOURNAL_STREAM=9:214146
Oct 07 12:57:29 ubu-18 caddy[12183]: {"level":"info","ts":1602075449.9490707,"msg":"using provided configuration","config_file":"/etc/caddy/Caddyfile","config_adapter":""}
Oct 07 12:57:29 ubu-18 caddy[12183]: {"level":"info","ts":1602075449.9529848,"logger":"admin","msg":"admin endpoint started","address":"tcp/localhost:2019","enforce_origin":false,"origins":["localhost:2019","[::1]:2019","127.0.0.1:2019"]}
Oct 07 12:57:29 ubu-18 caddy[12183]: {"level":"info","ts":1602075449.9534335,"logger":"http","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv0","https_port":443}
Oct 07 12:57:29 ubu-18 caddy[12183]: {"level":"info","ts":1602075449.9535916,"logger":"http","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
Oct 07 12:57:29 ubu-18 caddy[12183]: {"level":"info","ts":1602075449.9552305,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["golangiq.com"]}
Oct 07 12:57:29 ubu-18 caddy[12183]: {"level":"info","ts":1602075449.9630527,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc00021f500"}
Oct 07 12:57:29 ubu-18 caddy[12183]: {"level":"info","ts":1602075449.96366,"logger":"tls","msg":"cleaned up storage units"}
Oct 07 12:57:29 ubu-18 caddy[12183]: {"level":"info","ts":1602075449.9678338,"msg":"autosaved config","file":"/var/lib/caddy/.config/caddy/autosave.json"}
Oct 07 12:57:29 ubu-18 caddy[12183]: {"level":"info","ts":1602075449.9679778,"msg":"serving initial configuration"}
If you browse the ip of the VM now, you should see the caddy default home page.
We have installed Caddy v2.2.0 on Ubuntu 18 successfully. Now we will configure our Caddy 2 Web server to serve the FastAPI app running on port 8000 via a reverse proxy. To do so, lets edit the /etc/caddy/Caddyfile
.
Caddyfile
is a file without extension. Caddyfile holds the Caddy 2 Web server’s configuration.
Run the following command to edit Caddyfile
sudo nano /etc/caddy/Caddyfile
Replace the contents of the Caddyfile and it should look like below
:80 reverse_proxy 0.0.0.0:8000
From the above config we are telling Caddy 2 Web server to proxy the requests it receives on port 80, precisely ip:80 to the FastAPI application running on Gunicorn in the localhost on port 8000 via reverse_proxy directive available in Caddy 2.
Run the following command to restart Caddy service in order to take the changes effect.
sudo systemctl restart caddy
Configure Caddyfile with Custom Domain and HTTPS using Caddy 2
If you already own a custom domain lets say example.com, then you can point your domain’s A record with @ pointing to the VM’s public IP address, precisely IPv4 address. Or AAAA record with @ to point to the VM’s IPv6 address. Then you can update your Caddyfile to have the following configuration
# Caddyfile for Caddy 2 version
# Author: Navule Pavan Kumar Rao
# www.tutlinks.com
example.com:443 {
reverse_proxy 0.0.0.0:8000 {
header_up Host {http.request.host}
header_up X-Real-IP {http.request.remote}
header_up X-Forwarded-For {http.request.remote}
header_up X-Forwarded-Port {http.request.port}
header_up X-Forwarded-Proto {http.request.scheme}
}
tls [email protected]
log {
output file /var/log/caddy/access.log {
roll_size 1mb
roll_keep 4
roll_keep_for 24h
}
}
}
Two things to update in the above Caddyfile configuration that works for Caddy 2, one is to replace example.com with your domain and the other is to replace email [email protected] for tls
with your personal email id. GMail works too!
When you mention a custom domain in a Caddyfile configuration, the HTTPS is available by default and the certificates are provisioned via LetsEncrypt. Any updates related to certs will be emailed to the registered email mentioned in the Caddyfile for tls
directive.
Run the following command to create a directory named /var/log/caddy
for logs and set the user and group as caddy.
sudo mkdir -p /var/log/caddy && sudo chown caddy:caddy /var/log/caddy
We need to restart caddy.service
in order to reflect the changes every time we make to Caddyfile
. For that run the following command.
sudo systemctl restart caddy
Ensure to check the status of the Caddy service again. This step is necessary to see if any changes to Caddyfile are properly enabling the Caddy service back up and running again.
sudo systemctl status caddy
Alternatively you can also check the status of caddy service by running the following command.
journalctl -f -u caddy
Now from the an external PC browse http://IPAddress/notes/ (or https://example.com/notes/ if you have configured custom domain) and you should see the json notes in the response as shown below. Replace IPAddress with the IP address of your VM.
[{"id":1,"text":"Get Groceries from the store","completed":false}]
You can check the logs by the caddy web server by running the following command.
tail -f /var/log/caddy/access.log
Final Sanity Check and Troubleshooting deployment
To ensure our deployment is up and running again, reboot the VM and browse the http://IPAddress/notes/. You should be able to see the notes collection.
If you didn’t see the response as expected, troubleshoot at each layer as below
Troubleshoot PostgreSQL 13 Database installation
- Check the status of PostgreSQL database server
systemctl status postgresql
- if its not active, then run
sudo systemctl enable postgresql
andsudo systemctl start postgresql
- In case to look at the logs of PostgreSQL Service run the command
journalctl --unit=postgresql
- Check the database user and the database exists in PostgreSQL
- Check the database user has all privileges on database
- Check the connection string in FastAPI is properly built and is connecting to the installed PostgreSQL database.
Troubleshooting Gunicorn Service
- Check the status of Gunicon Service
systemctl status gunicorn.service
- If Gunicorn service is not active check the
ExecStart
,WorkingDirectory
andEnvironment
are properly configured with paths that actually exists in VM - To look at logs of gunicorn service run
journalctl --unit=gunicorn
sudo systemctl daemon-reload sudo systemctl enable gunicorn.service sudo systemctl start gunicorn.service
Troubleshoot Caddy 2 Web Server Setup
- Check the status of Caddy Service
systemctl status caddy.service
- In case to look at the logs of Caddy Service run the command
journalctl --unit=caddy
- If Caddy Service is not run then try running
sudo systemctl daemon-reload sudo systemctl enable caddy sudo systemctl start caddy
- Ensure the /etc/caddy/Caddyfile is having correct url to reverse_proxy with url and port that matches the url and port in gunicorn.service file. In our case its
0.0.0.0:8000
- Any changes made to the Caddyfile requires a restart of the Caddy Service in order for those changes to be effective.
- Run the command
caddy adapt --config /etc/caddy/Caddyfile --pretty
that will hint any syntax errors in Caddyfile. - Run the command to restart Caddy Service
sudo systemctl restart caddy
and check the status of the caddy servicesystemctl status caddy.service
Video Tutorial
Summary
To summarize what we have done in this tutorial, we
- Installed and Configured a PostgreSQL 13 Database Server on Ubuntu
- Created a database user and a database
- Granted all privileges to the user on the database
- Set up the deployment environment for FastAPI
- Installed python3-venv module
- Deployed FastAPI CRUD PostgreSQL repository on Ubuntu
- We did Clone FastAPI Repository from GitHub
- Installed Virtual Environment and dependencies from requirements.txt
- Run FastAPI on Gunicorn as a Systemd Unit Service
- Configured automatic log rotation for gunicorn access and error logs
- Installed Caddy 2 Web Server on Ubuntu 18
- Updated Caddyfile config for custom domain with HTTPS
- Expose FastAPI application as a reverse proxy via Caddy 2 Web server by configuring Caddyfile.
Where you want to go from here?
You can disable password based SSH access once the deployment is successful by disabling Port 22 on the VM.
You can configure Continuous Integration and Continuous Delivery by adding GitHub webhooks that automatically push code or configure travis to do the CI/CD for you.
If you plan to deploy your FastAPI on Cloud such as Heroku, GCP or Azure you can refer these articles and detailed videos here
Congratulations 🎉, you have successfully gained knowledge on deploying a Python based FastAPI application that talks to PostgreSQL database on a Ubuntu 18 Virtual Machine.
Pingback: Deploy FastAPI app on Google Cloud Platform – TutLinks
Pingback: Create and Deploy FastAPI app to Heroku – TutLinks
Pingback: Deploy FastAPI on Azure App Service – TutLinks