Posted on , Updated on 

Deploy Django Project with Nginx+uWSGI on CentOS 7.3

Background

I recently deployed two backend systems, both developed with Django, based on Python 3.6.5, and using virtualenv for environment management. This document records the complete deployment process for future reference.

Upgrade to Python 3.6.5

CentOS comes with Python 2.7 by default, so you need to upgrade to Python 3.6.5.

Download Python 3.6.5

Firstly, we need to install some required dependencies.

1
yum install openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel sqlite-devel gcc gcc-c++  openssl-devel libffi-devel python-devel mariadb-devel

Then, download, extract, and install Python 3.6.5.

1
wget https://www.python.org/ftp/python/3.6.5/Python-3.6.5.tar.xz
1
2
tar xvf Python-3.6.5.tar.xz
cd Python-3.6.5/
1
2
3
./configure
make
make install

Change the Default Python Version.

First, let’s check the current default Python version:

1
2
3
4
>>python -V
Python 2.7.5
>>python3 -V
Python 3.6.5

Backup the existing symbolic links:

1
mv /usr/bin/python /usr/bin/python.bak

Link Python to Python 3:

1
ln -s /usr/local/bin/python3 /usr/bin/python

Now, let’s check the default Python version 🎉:

1
2
>>python -V
Python 3.6.5

Configure yum

After upgrading Python, since the default Python is now pointing to Python 3, yum may not function properly. Modify /usr/bin/yum and /usr/libexec/urlgrabber-ext-down by changing #!/usr/bin/python to #!/usr/bin/python2.7, then save and exit.

Install and Start Nginx

1
2
yum install nginx
systemctl start nginx

At this point, accessing the public IP through a browser should display “Welcome to nginx.

Install and Create Virtual Environment

Install virtualenv

1
2
pip3 install virtualenv
pip3 install virtualenvwrapper

At this point, we attempt to use virtualenv commands but find that the workon command is not available.

1
2
>> workon
-bash: workon: command not found

Let’s modify the environment variables to solve this problem.

1
vim ~/.bashrc

Add two lines to .bashrc :

1
2
export WORKON_HOME=$HOME/.virtualenvs
source ./usr/local/bin/virtualenvwrapper.sh

Here, the export line specifies the path where virtualenv stores virtual environments, and the source line points to the script file for virtualenvwrapper. If you are unsure of the exact location, you can search for it:

1
2
3
>>cd ..
>> find -name "virtualenvwrapper.sh"
./usr/local/bin/virtualenvwrapper.sh

Finally, reload all configurations and commands from the .bashrc file to apply them in the current shell session.

1
source ~/.bashrc

Create Virtual Environment

1
mkvirtualenv envName

Next, on your local machine, export the local virtual environment and transfer it to the server. First, activate the local virtual environment:

1
workon localEnvName

Export all dependencies from the environment to requirements.txt.

1
2
3
4
5
6
7
8
9
10
11
12
13
C:\Users\JinTao>workon localEnvName
(localEnvName) C:\Users\UserName>pip list
Package Version
----------- -------
Django 2.2.2
mysqlclient 1.4.2
pip 19.1.1
pytz 2019.1
setuptools 41.0.1
sqlparse 0.3.0
wheel 0.33.4

(localEnvName) C:\Users\UserName>pip freeze > requirements.txt

Then, return to the cloud server, copy therequirements.txt file from your local machine to the server, and then install the same dependencies.

1
(envName)>>pip install -r requirements.txt

Here, we install uWSGI in the environment, which will be used later. uWSGI is a Web Server Gateway Interface (WSGI) that can be used to integrate Python web applications with web servers such as Nginx or Apache.

1
(envName)>>pip install uwsgi

Upload the Source Code

Upload the project to the /root/ directory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(envName) [root@VM_0_13_centos /]# cd ~
(envName) [root@VM_0_13_centos ~]# cd main_service_django/
(envName) [root@VM_0_13_centos main_service_django]# ls -l
total 28
drwxr-xr-x 5 root root 4096 Jul 7 18:32 apps
-rw-r--r-- 1 root root 0 Jun 28 20:07 db.sqlite3
drwxr-xr-x 2 root root 4096 Jul 7 18:32 log
drwxr-xr-x 3 root root 4096 Jul 7 18:32 main_service_django
-rw-r--r-- 1 root root 660 Jun 28 20:07 manage.py
drwxr-xr-x 2 root root 4096 Jul 7 18:32 media
drwxr-xr-x 5 root root 4096 Jul 7 18:32 static
drwxr-xr-x 2 root root 4096 Jul 7 18:32 templates

(envName) [root@VM_0_13_centos main_service_django]# python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
July 07, 2019 - 10:35:01
Django version 2.2.2, using settings 'main_service_django.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Test uWSGI:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
(envName) [root@VM_0_13_centos main_service_django]# uwsgi --http :8000 --module main_service_django.wsgi
*** Starting uWSGI 2.0.18 (64bit) on [Sun Jul 7 18:37:57 2019] ***
compiled with version: 4.8.5 20150623 (Red Hat 4.8.5-36) on 07 July 2019 10:29:05
os: Linux-3.10.0-514.21.1.el7.x86_64 #1 SMP Thu May 25 17:04:51 UTC 2017
nodename: VM_0_13_centos
machine: x86_64
clock source: unix
pcre jit disabled
detected number of CPU cores: 2
current working directory: /root/main_service_django
detected binary path: /root/.virtualenvs/环境名称/bin/uwsgi
uWSGI running as root, you can use --uid/--gid/--chroot options
*** WARNING: you are running uWSGI as root !!! (use the --uid flag) ***
*** WARNING: you are running uWSGI without its master process manager ***
your processes number limit is 15089
your memory page size is 4096 bytes
detected max file descriptor number: 100001
lock engine: pthread robust mutexes
thunder lock: disabled (you can enable it with --thunder-lock)
uWSGI http bound on :8000 fd 4
spawned uWSGI http 1 (pid: 8152)
uwsgi socket 0 bound to TCP address 127.0.0.1:39883 (port auto-assigned) fd 3
uWSGI running as root, you can use --uid/--gid/--chroot options
*** WARNING: you are running uWSGI as root !!! (use the --uid flag) ***
Python version: 3.6.5 (default, Jul 7 2019, 17:49:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]
*** Python threads support is disabled. You can enable it with --enable-threads ***
Python main interpreter initialized at 0x1377a80
uWSGI running as root, you can use --uid/--gid/--chroot options
*** WARNING: you are running uWSGI as root !!! (use the --uid flag) ***
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
mapped 72920 bytes (71 KB) for 1 cores
*** Operational MODE: single process ***
WSGI app 0 (mountpoint='') ready in 0 seconds on interpreter 0x1377a80 pid: 8151 (default app)
uWSGI running as root, you can use --uid/--gid/--chroot options
*** WARNING: you are running uWSGI as root !!! (use the --uid flag) ***
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI worker 1 (and the only) (pid: 8151, cores: 1)

Finally, add the public IP to ALLOWED_HOSTS in settings.py, and change Debug to False.

Configure Nginx

Create a directory for the config file:

1
2
mkdir config
cd config

Create uc_nginx.conf:

1
vim uc_nginx.conf

The content of the file is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# the upstream component nginx needs to connect to
upstream django {
# server unix:///path/to/your/mysite/mysite.sock; # for a file socket
server 127.0.0.1:8000; # for a web port socket (we'll use this first)
}
# configuration of the server

server {
# the port your site will be served on
listen 80;
# the domain name it will serve for
server_name 152.136.127.174 ; # substitute your machine's IP address or FQDN
charset utf-8;

# max upload size
client_max_body_size 75M; # adjust to taste

# Django media
location /media {
alias /root/main_service_django/media; # Django proj's media dir
}

location /static {
alias /root/main_service_django/static; # Django proj's static dir
}

# Finally, send all non-media requests to the Django server.
location / {
uwsgi_pass django;
include uwsgi_params; # the uwsgi_params file you installed
}
}

Set uc_nginx.conf as the configuration file:

1
cp uc_nginx.conf /etc/nginx/conf.d/

Restart Nginx:

1
systemctl restart nginx

Let Nginx to serve static files by adding the following to settings.py:

1
STATIC_ROOT = os.path.join(BASE_DIR, "static/")

Then run the following command:

1
python manage.py collectstatic

Configure uWSGI

1
2
cd config
vim uwsgi.ini

The configuration is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# mysite_uwsgi.ini file
[uwsgi]

# Django-related settings
# the base directory (full path)
chdir = /root/main_service_django/
# Django's wsgi file
module = main_service_django.wsgi
# the virtualenv (full path)
# process-related settings
# master
master = true
# maximum number of worker processes
processes = 10
# the socket (use the full path to be safe
socket = 127.0.0.1:8000
# ... with appropriate permissions - may be needed
# chmod-socket = 664
# clear environment on exit
vacuum = true
virtualenv = /root/.virtualenvs/envName

# logto = /tmp/mylog.log

Launch 🚀

1
2
3
uwsgi -i uwsgi.ini
// or
uwsgi -i uwsgi.ini &

Common Operations

  • Kill uWSGI (will automatically restart)

    1
    pkill -f uwsgi
  • Restart Nginx

    1
    2
    pkill -f nginx
    nginx
  • Check port usage

    1
    lsof -i:8000
  • Kill a process by PID

    1
    kill -9 PID