How to configure a Gunicorn service for a Django app on Ubuntu

June 5, 2017 12:45pm
by Thomas Tran



In this tutorial, I'll go over how to configure Gunicorn on an Ubuntu server. This is meant for both (semi) UNIX beginners and UNIX experts looking to refresh their skills, so I'll try to use terminologies which accommodate both groups.

Gunicorn can be used to serve the app to a port. In this tutorial, however, we'll be serving the app to a socket instead. This prevents many overheads associated with serving the app to a port. In the next tutorial, we'll find out how to use Nginx as a reverse proxy to connect our socket (and thus app) to the external world. We'll also daemonize our gunicorn process using systemd to ensure that the app starts when the machine restarts.


Create a virtualenv

To kick things off, we'll update all of our local Ubuntu packages with the command

sudo apt-get update

Next, we'll install virtualenv, a package which isolates Python packages in our project from other dependencies system-wide. This will prevent package version conflict and other complications which we (definitely) want to avoid. We use pip, a package manager for Python. You can use other managers, such as yum, but pip seems to be more mainstream and accessible.

pip install virtualenv

cd into a local directory close to where your project is located. We'll now create the virtualenv itself:

virtualenv djangoapp_env

...where djangoapp is the name of the current project.

Assuming you are in the folder that contains djangoapp_env, activate the virtualenv with the following command:

djangoapp_env/bin/activate

Make a note of where your project is located. In this case, our project is located inside /var/www/djangoapp.


Install project dependencies

cd into your project's manage.py directory and perform the following command:

pip install -r requirements.txt

This will install all dependencies required by your project into the djangoapp_env. Remember, all packages inside the djangoapp_env are isolated from the system-wide packages.


Install and test gunicorn

Next, install gunicorn with the following command:

pip install gunicorn

This will install gunicorn inside your djangoapp_env.

Now that we have set up our Django project, and installed all necessary dependencies, it's time to actually work with gunicorn.

To see that gunicorn works with our Django app, we'll test run it on the command line:

gunicorn --bind 0.0.0.0:8000 djangoapp.wsgi

This will by default serve our app on all IPv4 addresses on our local machine through port 8000. Open a browser at that address to confirm that the app is there. Under the hood, gunicorn invokes the application callable that is exposed by the Django app inside wsgi.py file.

To bind our app to a socket instead of port 8000, we can run gunicorn with the following command:

gunicorn --bind unix:/var/www/djangoapp/djangoapp.sock

...where /var/www/djangoapp/djangoapp.sock is the location where you want the socket file to be. It can be anywhere on the machine, but it's good to place it somewhere that makes sense!


Create the systemd service

Now that we've tested that gunicorn works and now that we've learned how to serve our Django app to a socket, we'll create a service which will allow gunicorn to run in the background.

cd into /etc/systemd/system/. This directory contains all systemd services. Next, let's create a new service to run our Django app. A service is just a text file, so let's create one with the following command:

sudo vim gunicorn.service

This will tell vim (a command-line text editor) to create a new file called gunicorn.service inside the current directory. If you don't have vim or aren't comfortable with it, feel free to use other text editors.

Next, paste the following into the file:

[Unit]
Description=Djangoapp gunicorn daemon :-)
After=network.target

[Service]
User=djangoappuser
Group=www-data
WorkingDirectory=/var/www/djangoapp/
ExecStart=/var/www//djangoapp_env/bin/gunicorn --workers 3 --access-logfile /var/www/logs/djangoapp-access.log --error-logfile /var/www/logs/djangoapp-error.log --bind unix:/var/www/djangoapp/djangoapp.sock djangoapp.wsgi:application

[Install]
WantedBy=multi-user.target

Notice that every path inside the service file is absolute not relative. Save this file and then quit (ESC, then the command :wq).


Run the service

Then we can start the gunicorn service with the following commands:

sudo systemctl start gunicorn
sudo systemctl enable gunicorn

This will start gunicorn in the background. In the next tutorial, we'll find out how to use nginx as a reverse proxy to connect our socket to the external world.


Useful commands and troubleshooting

When working with systemctl, some systems allow you to use the service command to control services. In our case, the following comands are available to us:

service gunicorn start
service gunicorn stop
service gunicorn restart (which is just really a shortcut for the two commands above)

Also, systemd unit files (such as our service file above) need to be reloaded every time they are changed. This will reload all unit files and recreate the daemon. To reload the service file, use the following command:

systemctl daemon-reload

You'll need to perform an additional service gunicorn restart if you want to restart the service itself.

If you run into permission errors, you probably have insufficient rights to run gunicorn with the djangoappuser. To fix this, change file permissions of your project to allow djangoappuser access.