3.1 How To Deploy Python Apps Using MongoDB uWSGI and Nginx
3.1 How To Deploy Python Apps Using MongoDB uWSGI and Nginx
nginx
This tutorial covers the basic steps of deploying a Python application onto a public server
using MongoDB, uWSGI, and nginx. We will be using a sample project, a Flask web server, for
demonstration, however, the deployment process should remain similar for any other Python
applications. Our sample project can be found here: https://github.com/tecladocode/price-
of-chair-deployment.
In this tutorial, we will not cover how to set up a server on any hosting platforms, however, if
you are looking for such a tutorial, you may take a look at this one: DigitalOcean Tutorial, in
which you will learn the basics on setting up a server from the beginning on a cloud hosting
platform. The procedure should be similar for setting up server on other platforms as well,
such as AWS (Amazon Web Service).
If you are a first time learner, we highly recommend you to follow through the whole tutorial
so that you can get familiar with it and may be less likely to run into problems.
You will be asked for the root password (or the SSH key if you have set it up previously).
Beware that SSH command only works on UNIX , not on Windows . However, there are
plenty of software that you can use to SSH from Windows, PuTTy is a popular choice:
After connecting to our server and logging in as the root user, it is recommended to run
the below command first to get all the available updates:
apt-get update
Note that this is a just an example to install different packages using one command, we will
see real use cases in the following sections.
In this section, we will create a user named johndoe . You may choose any name you want,
just remember to swap johndoe with your username for each command and configuration.
We can create a new user johndoe with the following command:
adduser johndoe
You will be asked to enter and confirm the password for this user, and then provide some info
about this user. Notice that you can leave the info sections blank if you want to. And if you
entered unmatching passwords, just complete the info section and we can change the
password later by using the command:
passwd johndoe
Since we will be logging in as johndoe for most of the time in the future, we will want it to
have some "extra power", that is, temporarily acting as a super user. To do this, we need to
run the command:
visudo
first, and we will see a text file popping up. Then we navigate to the lines containing:
You can do this with the arrow keys. We need to add a new line for our user in this section:
# User privilege specification
root ALL=(ALL:ALL) ALL
johndoe ALL=(ALL:ALL) ALL
Remember that the ALL has to be all uppercase, otherwise it will raise syntax error.
After adding this line, use ctrl + o to save and press ENTER to overwrite, then press
ctrl + x to quit.
Next, we want to allow us to login as johndoe using SSH, and we may also want to disable
login as root from SSH to make our server more secure. To do this, use the command:
vi /etc/ssh/sshd_config
And we will be prompted with another text file. Navigate to the section which contains:
# Authentication
PermitRootLogin yes
Press i on your keyboard to enter insert mode and change the yes to no to disallow
login as root.
Important: make sure to set it as yes so that you can use your password to login in the
future. It should be set to yes already, however, there are some platforms that enforces
SSH key authentication and set it to no instead.
Then go to the bottom of the file and add the following lines:
AllowUsers johndoe
For this section, if you already have other users on the server, make sure to include them as
well. On AWS, for instance, a user named ubuntu is initialized and used to login for the first
time. If you choose to create a new user, say johndoe , then you will need to add them
together into AllowUsers :
Next, press Esc to quit insert mode, press : (colon) to enable the command function and
enter wq to write and quit (after hitting ENTER to confirm).
Some other useful vi commands are: :q to quit without modification and :q! to force quit
and discard changes.
Now we've created a new user johndoe and enabled both its super user privilege and SSH
access.
MongoDB
Installing MongoDB
MongoDB will start when your server restarts. You can prevent that default behaviour by
doing:
First, we create a folder called pricing-service for our app, since our sample project is a
REST API which manages items of stores. We create this folder using the following command:
The folder is owned by the root user since we used sudo to create it. We need to transfer
ownership to our current user:
Remember that johndoe is the username in our tutorial, make sure you change it to yours
accordingly. The same goes for pricing-service .
cd /var/www/html/pricing-service/
git clone https://github.com/tecladocode/price-of-chair-deployment .
Note that there's a trailing space and period ( . ) at the end, which tells Git the
destination is the current folder. If you're not in this folder /var/www/html/pricing-
service/ , remember to switch to it or explicitly specify it in the Git command. And for the
following commands in this section, we all assume that we are inside the
folder /var/www/html/pricing-service/ unless specified otherwise.
mkdir log
touch log/emperor.log
Next, we will install pipenv , which is a python library used to create virtual environment.
Since we may want to deploy several services on one server in the future, using Pipenv allows
us to create independent environment for each project so that their dependencies won't
affect each other. We may install pipenv using the following command:
After it is installed, we can use it to create our environment and install the requirements:
pipenv install
uWSGI
In this section, we will be using uWSGI to run the app for us, in this way, we can run our app
in multiple threads within multiple processes. It also allow us to log more easily. More details
on uWSGI can be found here.
[Unit]
Description=uWSGI Pricing Service
[Service]
User=johndoe
Group=johndoe
WorkingDirectory=/var/www/html/pricing-service
Environment=MONGODB_URI=mongodb://127.0.0.1:27017/fullstack
ExecStart=/home/johndoe/.local/bin/pipenv run uwsgi --master --emperor /var/
www/html/pricing-service/uwsgi.ini --die-on-term --uid johndoe --gid johndoe
--logto /var/www/html/pricing-service/log/emperor.log
Restart=always
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all
[Install]
WantedBy=multi-user.target
We will explain the basic idea of these configs. Each pair of square brackets [] defines
a section which can contain some properties.
The Unit section simply provides some basic description and can be helpful when looking
at the logs.
If we want to add multiple environment variables, we just need to add multiple lines of
the Environment entry following the syntax:
Environment=key=value
Environment=key=value
Environment=key=value
...
The ExecStart property informs uWSGI on how to run our app as well as log it.
At last, the WantedBy property in Install section allows the service to run as soon as the
server boots up.
Hint: after editing the above file, press ESC to quit insert mode and use :wq to write and
quit.
Configuring uWSGI
Before creating or editing the configuration file, run this command and remember the path it
tells you. You'll need it to tell uwsgi where the Python home is:
pipenv --venv
Our next step is to configure uWSGI to run our app. To do so, we need to create a file named
uwsgi.ini with the following content:
[uwsgi]
base = /var/www/html/pricing-service
app = app
module = %(app)
home = /path/to/your/venv
pythonpath = %(base)
socket = %(base)/socket.sock
chmod-socket = 777
processes = 8
threads = 8
harakiri = 15
callable = app
logto = %(base)/log/%n.log
Note that you should change the base folder accordingly in your own app. For the second
entry, run is referred to the run.py in our sample app, which serves as the entry point of
our app, so you may need to change it accordingly in your own project as well. We defined
the socket.sock file here which will be required by the nginx later. The socket file will
serve as the connection point between nginx and our uWSGI service.
We asked for 8 processes with 8 threads each, but you may adjust them according to your
server capacity and data volume. The harakiri is a Japanese word for suicide, so in here it
means after how long (in seconds) will the emperor kill the thread if it has failed. This is also
an advantage we have with uWSGI , it allows our service to be resilient to minor failures. And
it also specifies the log location.
And at last, after saving the above file, we use the command below to run the uWSGI service
we defined earlier:
And we should be able to check the uWSGI logs immediately to make sure it's running by
using the command:
vi /log/uwsgi.log
But if there are any error sin our code, it will also be reflected in the log.
Nginx
Nginx (engine x) is an HTTP and reverse proxy server, a mail proxy server, and a generic
TCP/UDP proxy server. In this tutorial, we use nginx to direct traffic to our application.
Nginx can be really helpful in scenarios like running our app on multiple threads, and it
performs very well so we don't need to worry about it slowing down our app. More details
about nginx can be found here.
Installing nginx
If not, we will enable it later. Before that, let's add some new rules:
Important: the second line, adding SSH rules, is not related to nginx configuration, but
since we're activating the firewall, we don't want to get blocked out of the server!
If the UFW (Ubuntu Firewall) is inactive, use the command below to activate it:
Before deploying our app onto the server, we need to configure nginx for our app. Use the
below command to create a config file for our app:
sudo vi /etc/nginx/sites-available/pricing-service.conf
Note that pricing-service is what we named our service, you may change it accordingly,
but remember to remain consistent throughout the configurations.
Next, we input the below text into pricing-service.conf file. Remember to change your
service name accordingly in this file as well.
server {
listen 80;
real_ip_header X-Forwarded-For;
set_real_ip_from 127.0.0.1;
server_name localhost;
location / {
include uwsgi_params;
uwsgi_pass unix:/var/www/html/pricing-service/socket.sock;
}
The above config allows nginx to send the request coming from our user's browser to our
app. It also sets up some error pages for our service using nginx predefined pages.
And at last, in order to enable our configuration, we need to do something like this:
sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /etc/nginx/sites-available/pricing-service.conf /etc/nginx/sites-
enabled/
We need to remove the default file since nginx will look at this file by default. We want
nginx to look at our config file instead, thus we added a soft link between our config file
and the sites-enabled folder.
Finally, we can launch our app! We can do so by starting the nginx and uWSGI services we
defined (we already started the uWSGI service in the previous section).
sudo systemctl start nginx
If any of these services is already running, you may use the below commands (taking nginx
for example) to reload and restart it so that it has the latest changes:
sudo vi /etc/crontab
And in it, place the script you want to run every hour. We will be running our
alert_updater.py file from our code. Do not delete any existing lines, so we have just
added the one for our alert updater.
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
Deployment wrap-up
As the tutorial is very detailed, you may find it a bit hard to put the pieces together. Here's a
quick wrap-up that may help you sort things out.