I have just (or maybe not yet) gone through the panic dealing with all the configuration of Django framework on Mac OS X. This post details the steps to create a Django project and deploy it with uWSGI in nginx. Since the documentations and tutorials online mainly relate to Linux system, I hope this post will help you if you love Mac and want deploy Django apps on it.

Prerequisite

  1. python 2.7 & pip: brew install python
  2. nginx: brew install nginx
  3. virtualenv: pip install virtualenv

What are nginx and uWSGI? and why should I use them?

nginx is a web server. A web server accepts the client’s request and send reponse to them. For static files such as images and static html pages, just put all the stuff in the web server is enough, but it’s not capable of sending dynamic pages. A web app serves client the content, e.g., it retrieves records from the database and organizes them into a html table. Django framework plays this kind of role. The web server needs a mechanism or protocal to talk to web app to get the content, and that’s what the WSGI serves for. WSGI is short for Web Server Gateway Interface, which is a specification that describes how a web server communicates with web applications. uWSGI is one of its implementations. Although Django itself contains a web server, it’s unsafe and doesn’t go through the performance tests. As Django puts it:

We’re in the business of making Web frameworks, not Web servers, so improving this server to be able to handle a production environment is outside the scope of Django.

WSGI is an official python standard and lots of python web components are based on it. With WSGI, you can switch to any web server which supports it. nginx is much more lightweight than Apache, and has lots of 3rd party modules. Compared to Apache, it’s easy to configure and deploy. For other comparisons, please see Apache vs Nginx: Practical Considerations.

This post uses nginx as the web server, and uses uWSGI to communicate with nginx and Django.

Start the project and install all the dependencies

Here I use virtualenv to create isolated python environments. Here are the steps:

  1. Make a diretory named mysite and cd into it.
  2. Create a virtual environment with virtualenv: virtualenv venv.
  3. Activate the virtual environment: source venv/bin/activate.
  4. Install django and uwsgi: pip install django uwsgi.
  5. It’s better to use a requirements.txt file to log all the packages needed in the project: pip freeze > requirements.txt. So next time you just need to run pip install -r requirements.txt to install all the dependencies at once. And if you are using git, you can put a venv item in .gitignore to prevent git tracking the unnecessary dependencies.

Start a Django project and collect the static and media files

nginx needs to know where the static files (e.g., images, js scripts, etc.) locate, and we need configure to Django.

  1. Start a Django project: django-admin.py startproject mysite
  2. vim or edit mysite/settings.py, add the following configurations:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    STATIC_URL = '/static/'
    STATIC_ROOT = os.path.join(BASE_DIR, 'static')
    STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static1"),
    os.path.join(BASE_DIR, "static2"),
    ]
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
    MEDIA_URL = '/media/'

    STATICFILES_DIRS lists all the folders where Django will search for static files, and STATIC_ROOT points to the folder Django collects all the static files. The media folder is where Django saves the files the clients upload.

  3. Set DEBUG as False since this post details the deployment procedure, and set ALLOWED_HOSTS = ['127.0.0.1', 'localhost'].
  4. Create static1, static2 and media folders and put some images into them to test whether we configure Django correctly. Here I put left.jpg into static1, middle.jpg into static2, and right.jpg into media folder.
  5. When make changes to static files, make sure run python manage.py collectstatic to tell Django to copy all the static files into static folder, where nginx will search.
  6. Run python manage.py runserver --insecure and you shall see all the images you just put into the static and media folders by visiting http://localhost:8000/static/left.jpg, http://localhost:8000/static/middle.jpg and http://localhost:8000/media/right.jpg.

We need to tell nginx listen to which port, how to talk to Django and where to find the static files. Create a mysite_nginx.conf file in the folder where manage.py locates, with the following content:

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
# the upstream component nginx needs to connect to
upstream mysite {
# server 127.0.0.1:8001;
server unix:///tmp/mysite.sock; # for a file socket, more effective
}
# configuration of the server
server {
# the port your site will be served on
listen 8888;
server_name localhost;
charset utf-8;
# max upload size
client_max_body_size 75M; # adjust to taste
# Django media
location /media {
alias /path/to/your/media; # your Django project's media files - amend as required
}
location /static {
alias /path/to/your/static; # your Django project's static files - amend as required
}
# Finally, send all non-media requests to the Django server.
location / {
uwsgi_pass mysite;
include /path/to/your/uwsgi_params; # the uwsgi_params file you installed
}
}

Here we let nginx talk to Django through a unix file /tmp/mysite.sock, tell nginx listen to 8888 port, and instruct nginx to find the static and media files at where we have configured in the previous steps. nginx needs an uwsgi_params file, and you can find it at https://github.com/nginx/nginx/blob/master/conf/uwsgi_params.

We need to create a soft link to mysite_nginx.conf in /usr/local/etc/nginx/servers to make nginx find the configuration file and apply it:

1
ln -sf /path/to/your/mysite_nginx.conf /usr/local/etc/nginx/servers

In /usr/local/etc/nginx/nginx.conf, there is a line: include servers/*;, so nginx knows to find and apply the configuration in the servers folder.

Create the uWSGI configuration file

Usually we put all the uWSGI configuration staff into a .ini file since there’re too many options if we use the command line args. Create a mysite_uwsgi.ini, and add the following into it:

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
# mysite_uwsgi.ini file
[uwsgi]
# Django-related settings
# the base directory (full path) where settings.py locates
chdir = /path/to/mysite/mysite
# Django's wsgi file
module = mysite.wsgi
# the virtualenv (full path)
home = /path/to/mysite/venv
# process-related settings
# master
master = true
# maximum number of worker processes
processes = 10
# the socket (use the full path to be safe)
socket = /tmp/mysite.sock
# ... with appropriate permissions - may be needed
chmod-socket = 664
# clear environment on exit
vacuum = true
# create a pidfile
pidfile = /tmp/mysite.pid
# background the process & log
daemonize = uwsgi.log

Since we use virtualenv, we need to use home option to tell uWSGI where to find the dependent packages. Here we use socket to designate the unix file where uWSGI uses to talk to nginx. In the previous steps, I said nginx talks to Django, and that’s inaccurate. Actually, nginx talks to uWSGI and uWSGI talks to Django:

the web client <-> the web server <-> the socket(unix file here) <-> uWSGI <-> Django

We use a pidfile option to tell uWSGI to write down its pid when it is launched. And we can use uwsgi --stop /tmp/mysite.pid to stop it. We use daemonize option to background the process and log uWSGI’s output into uwsgi.log.

Launch nginx and uWSGI

All the configuration work is done! Now we need to launch nginx and uWSGI to check whether it works:

  1. Run nginx to launch nginx.
  2. Run uwsgi --ini /path/to/mysite_uwsgi.ini to start uWSGI.
  3. Visit http://localhost:8888/static/left.jpg to check whether it works.

Create deploy, launch and stop shell scripts

There are some flaws in the previous configuration files. All the paths are hard coded. We can use shell scripts to avoid that.

Create deploy.sh

In the folder where manage.py locates, create a deploy.sh file with the following content:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#!/usr/bin/env bash
ROOT_PATH=`pwd`
NGINX_CONFIG_FILE=mysite_nginx.conf
NGINX_SITES_CONFIG=/usr/local/etc/nginx/servers
UWSGI_CONFIG_FILE=mysite_uwsgi.ini
echo """
# the upstream component nginx needs to connect to
upstream mysite {
# server 127.0.0.1:8001;
server unix:///tmp/mysite.sock; # for a file socket, more effective
}
# configuration of the server
server {
# the port your site will be served on
listen 8888;
server_name localhost;
charset utf-8;
# max upload size
client_max_body_size 75M; # adjust to taste
# Django media
location /media {
alias $ROOT_PATH/media; # your Django project's media files - amend as required
}
location /static {
alias $ROOT_PATH/static; # your Django project's static files - amend as required
}
# Finally, send all non-media requests to the Django server.
location / {
uwsgi_pass mysite;
include $ROOT_PATH/uwsgi_params; # the uwsgi_params file you installed
}
}
""" > $NGINX_CONFIG_FILE
echo """
# mysite_uwsgi.ini file
[uwsgi]
# Django-related settings
# the base directory (full path)
chdir = $ROOT_PATH
# Django's wsgi file
module = mysite.wsgi
# the virtualenv (full path)
home = $ROOT_PATH/../venv
# process-related settings
# master
master = true
# maximum number of worker processes
processes = 10
# the socket (use the full path to be safe)
socket = /tmp/mysite.sock
# ... with appropriate permissions - may be needed
chmod-socket = 664
# clear environment on exit
vacuum = true
# create a pidfile
pidfile = /tmp/mysite.pid
# background the process & log
daemonize = uwsgi.log
""" > $UWSGI_CONFIG_FILE
if [ ! -d "$NGINX_SITES_CONFIG" ]; then
mkdir -p "$NGINX_SITES_CONFIG"
fi
ln -sf $ROOT_PATH/$NGINX_CONFIG_FILE $NGINX_SITES_CONFIG

It just replace all the hard coded paths with $ROOT_PATH.

Create start.sh

In the folder where manage.py locates, create a start.sh file with the following content:

1
2
3
4
5
6
7
#!/usr/bin/env bash
# start nginx
sudo nginx
# start uwsgi
uwsgi --ini mysite_uwsgi.ini

Just launch nginx and uWSGI, no big deal.

Create stop.sh

Still in the folder where manage.py locates, create a stop.sh file with the following content:

1
2
3
4
5
6
7
#!/usr/bin/env bash
# shut down uwsgi
uwsgi --stop /tmp/mysite.pid
# gracefully stop nginx
sudo nginx -s quit

stop.sh will stop uWSGI and nginx.

Now you can place your project anywhere you like, and run ./depoly.sh to create the configuration files and deploy it to nginx, run ./start.sh to start the service and run ./stop.sh to stop the service.

Some notes

  1. Make sure nginx has the permissions to visit the static and media folder. Modify /usr/local/etc/nginx/nginx.conf and change the user option properly, e.g user sean staff.
  2. mysite server uses the 8888 port, you can change it in deploy.sh if it has already been taken.

Conclusion

This post details the steps to start a Django project from scratch and deploy it to nginx. You can clone the sample project at Sean-Lan/mysite, and any feedback is welcome!

Reference

[1] Setting up Django and your web server with uWSGI and nginx

[2] Managing static files (e.g. images, JavaScript, CSS)

[3] The uWSGI project

[4] Deploy Django + nginx + gunicorn on Mac OSX (Part 1)

[5] Deploy Django + nginx + gunicorn on Mac OSX (Part 2)

[6] Django gives Bad Request (400) when DEBUG = False