How to configure a Gunicorn service for a Django app on Ubuntu
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.