Deploy Python APIs on CentOS Webservers | Towards Data Science

The web runs on Linux, and we are currently living in the age of APIs. These two facts alongside Python being the most popular programming language in the world make knowing how to deploy a Python API via Linux an invaluable skill.
API development in Python is typically regulated to Django, Flask, or FastAPI, alongside a few other frameworks. The fastest of those for API development is the fittingly named FastAPI library.
In this article, we will produce a simply API with FastAPI — and learn how to deploy it on a Linux webserver (CentOS) using Uvicorn, Gunicorn, systemd, and (optionally) NGINX. We will cover:
> Prerequsites  1. EPEL  2. Python  3. NGINX> Creating a Python Environment> Python Setup  - pip install  - API Code> Systemd  - The Gunicorn Service  - Common Errors> NGINX Setup (Bonus)  - Before We Start  - NGINX> Other Useful Articles
Before setting up and deploying our API, there are a few things we need to install on our Linux machine. Those are:
EPELPythonNGINX
EPEL
Python
NGINX
EPEL is installed with:
sudo yum install epel-release
EPEL stands for Extra Packages for Enterprise Linux. It is a group that curates and maintains a group of Linux packages for use in Fedora (and it’s derivatives, like CentOS).
The reason we want EPEL is because it allows us to install a lot of open source packages via yum.
Now we have EPEL installed, we can go ahead and quickly install Python with just:
sudo yum install python36
We can confirm Python has been installed correctly by typing python3 -V:
All we need to install nginx is:
sudo yum install nginx
We use NGINX to act as a ‘reverse proxy’ for our server. This means that NGINX creates a shield to act as a gateway between the internet and our server.
Our application can work without NGINX, but using it gives us several benefits, namely:
We can hide the characteristics of our ‘origin’ server.
Better protection against DoS/DDoS and malware.
https://www.nginx.com/resources/glossary/nginx/
https://en.wikipedia.org/wiki/Reverse_proxy
Ideally, we want to separate the Python installation we use for our API from the other versions of Python on our server. We do this by using a separate version of Python within a virtual environment (venv).
We need to install virtualenv first, it is likely to be installed already — but we check by trying to install with:
python3 -m pip install virtualenv
Once we are happy that virtualenv has been installed we can create the directory to host our venv (and code):
mkdir ~/project-dircd ~/project-dir
Now we install a venv (along with its very own version of Python) with:
virtualenv <env-name>
Whenever we want to switch into this virtual env — we type source apienv/bin/activate. We need to have the environment active for our Python packages installations.
Once we have our venv and have activated it with source <env-name>/bin/activate we can pip install the packages needed to run our API:
pip install fastapi gunicorn uvicorn uvloop httptools
Now we can write the code for our API. Of course, if you have your own API script — use that. We will enter the code in a local file api.py by opening Vim:
vim api.py
Followed by i to begin editing the file. We want it to look like this:
Once complete, we save and exit by typing ESC followed by :wq and ENTER. With this, our API is set up and will be run using Uvicorn.
We also need Gunicorn to handle multiple instances of our Uvicorn workers, which will allow multiple instances of our API to run in parallel. Back in the terminal, we can initialize Gunicorn handling our Uvicorn workers with:
gunicorn api:app -w 2 -k uvicorn.workers.UvicornWorker -b "0.0.0.0:5000"
Here, we are initializing Gunicorn with our code in api, with the API instance app.
There are two workers specified by -w 2. The type of workers to use are provided by Uvicorn, which we specify with -k uvicorn.workers.UvicornWorker.
Finally, we bind our Gunicorn instance to our outward-facing port 5000 with -b “0.0.0.0:5000”.
Heading over to your server address example.com:5000 should show the API response in your browser. At this point, you could call it a day and leave it.
Although I’m sorry to say, we’re not quite there yet.
Go ahead and play around with your API — after that, we have a few more things to do:
Setting up our Gunicorn instance to run in the background and initialize on boot.
Creating a socket (like another internal information gateway) for Gunicorn and NGINX to communicate through.
Setting up NGINX!
We use systemd to set our Gunicorn instance to run in the background. As soon as the server boots, our API will be up and running.
The name systemd refers to a software suite used in Linux which acts as a unified system and service manager. We will use it to run our API, which will hum away in the background of our server. The systemd services directory can be found in:
/etc/systemd/system
Here, we create a new file that will run our Gunicorn instance. Type sudo vim <service-name>.service to begin editing the new service.
Here, we want to write something like this:
There’s a lot going on here, so let’s break it down.
We save and exit again with ESC followed by :wq and ENTER. Next, we initialize and check the status of the service with:
sudo systemctl start apisystemctl status api
If you are getting an error like this:
There are a few common issues. It is most likely:
Service file syntax is incorrect.
The paths specified are wrong (is your path /home/user/<user> or just /home/<user>?).
Gunicorn file permissions are wrong.
The solve the latter, you may need to head back over to ~/api/apienv/bin and change the owner of the gunicorn file with:
sudo chown <user> gunicorn
Once it is running you should see this:
At this point, our code will be running. You should be able to head over to example.com:5000 and see the response from your API!
The remainder of the article is a bonus, we will be setting up NGINX, which we can think of as essentially a network connection manager — it is very useful, but can be fiddly depending on your current setup. The choice is yours.
NGINX acts as our outward-facing gateway — the NGINX reverse proxy. Set-up for this looks intimidating, but trust me it really is not too difficult.
First, we need to make a few modifications to the ports being used by our API so that they are ready for communicating with NGINX.
First, we need to create an internal socket that NGINX will use to communicate with Gunicorn. It essentially acts as an internal gateway between NGINX and Gunicorn.
All we need to do to create it is modify our Gunicorn command in api.service. We add -m 007, and bind to unix:api.sock rather than "0.0.0.0:5000". This will create the websocket in the working directory /home/user/api:
ExecStart=/home/user/api/apienv/bin/gunicorn -w 3 -b unix:api.sock -m 007 -k uvicorn.workers.UvicornWorker api:app
After editing our service file, we need to reload and restart it with:
sudo systemctl daemon-reloadsudo systemctl restart speculate_api
The outward-facing entrance to our API is given by server: server_name in /etc/nginx/conf.d/<domain>.conf like so:
Once ready, save and exit (ESC, :wq, ENTER). We now set up NGINX permissions to allow access to our user directory to the nginx user:
sudo usermod -a -G <user> nginxchmod 710 /home/<user>
And then we test that our new NGINX configuration works with:
sudo nginx -t
Hopefully, you will see this:
It is also worth checking that this new configuration file is included within the master nginx.conf file. Within /etc/nginx/nginx.conf check that all .conf files inside the /conf.d directory of included with:
include /etc/nginx/conf.d/*.conf;
Within the http {...} section:
If everything is correct and works, we can restart NGINX with:
sudo systemctl daemon-reloadsudo systemctl restart nginx
You should now be able to access the API again at example.com:5000!
That’s all for this article on deploying an API with Python to the web! We’ve covered a lot, including:
Essentials of EPEL, Python, and NGINX in APIs
Python environments
API packages and code
Gunicorn and Uvicorn
Linux systemd
NGINX configuration
All of this is a lot, and not something that can easily be worked through in one sitting (by most people).
As you work through this article, I would definitely recommend reading through a few other articles that I have included below. These cover much of what we have covered here, but either in more depth or for different set-up/scenarios.
I hope you enjoyed this article! If you have any questions, let me know via Twitter or in the comments below. If you’d like more content like this, I post on YouTube too.
Thanks for reading!
How To Edit The Sudoers File
Deploying Django >3.1 (async) with Uvicorn and Nginx
How To Serve Flask Applications with Gunicorn and Nginx on CentOS 7
Configure NGINX to Host Multiple Subdomains
How to Install NGINX as Reverse Proxy for Apache on Ubuntu 15.10
🤖 70% Discount on the NLP With Transformers Course