Gunicorn Checklist

Here is a summary of how I set up a new Python-3-backed server on my hobby server.

An appendix to Part 3 Let’s Try Gunicorn Instead of the Running to Stand Still series.

Earlier notes to myself:

On the Server as the Server Account

I use names without dots for the name of the site for simplicity, so for example spreadsite.org uses the site name spreadsite. Lets keep that in a variable so I can copy-paste these commands verbatim.

SITE=jeremyday

The files for the site are owned by a user account named after the site. Start by logging in as that user and creating its virtualenv.

mv ~/virtualenvs/$SITE ~/virtualenvs/before-$(date +%Y-%m-%d).$SITE
virtualenv --python $(which python3) ~/virtualenvs/$SITE

Install Gunicorn in the same virtualenv that it will be running code from:

. ~/virtualenvs/$SITE/bin/activate
pip install gunicorn

As User with Sudo Access

Create a directory in /service as follows:

SITE=jeremyday
HOST=jeremyday.uk
PORT=8003
sudo mkdir /service/$SITE

Create logging directory (just in case—if things work as planned then logs go in /var/log/gunicorn):

sudo mkdir -p /service/$SITE/log/main
cat /service/alleged/log/run | sudo tee /service/$SITE/log/run
sudo chmod +x /service/$SITE/log/run

Create an environment directory:

sudo mkdir /service/$SITE/env
echo $LANG | sudo tee /service/$SITE/env/LANG
echo /home/$SITE/static | sudo tee /service/$SITE/env/STATIC_ROOT
echo //static.$HOST/ | sudo tee /service/$SITE/env/STATIC_URL
python -c 'import uuid; print(uuid.uuid4())' | sudo tee /service/$SITE/env/SECRET_KEY

Copy the run script and update it for the new service account:

sed -e s/alleged/$SITE/ -e s/8001/$PORT/ /service/alleged/run | sudo tee /service/$SITE/run
sudo chmod +x /service/$SITE/run

Local Tasks

Check there are no editing links in Pipfile. Then update requirements.txt.

pipenv run python -Wall manage.py test &&
    pipenv lock -r > requirements.txt
git commit -a -m 'Update requirements.txt'

Update fabfile.py to no longer tickle the uWSGI emperor. This is also the time to prefix any ./manage.py commands with envdir /service/SITE/env. Something like this:

with prefix('. /home/{0}/virtualenvs/{1}/bin/activate'.format(env.site_name, env.virtualenv)):
    run('pip install -r requirements.txt')
    run('envdir /service/%s/env ./manage.py collectstatic --noinput' % env.site_name)

This is needed so that collectstatic knows the correct directories to copy to.

It now should be possible to update the code on the server with:

fab deploy

At this point the site might still be running but only because the emperor is still serving the old environment.

Check it Runs

Ideally it will already be running in supervise and you can check as follows:

cd /tmp
wget http://localhost:$PORT/

Where $PORT is the port number in the run file.

If this fails you can check the server by running Gunicorn extemporaneously like so:

cd /tmp
envdir env /home/$SITE/virtualenvs/$SITE/bin/gunicorn \
    --name $SITE-gunicorn \
    --chdir /home/$SITE/Sites/$SITE \
    --bind 127.0.0.1:7001 \
    --access-logfile $SITE-access.log \
    --error-logfile $SITE-error.log \
    $SITE.wsgi

And in another window

cd /tmp
wget http://localhost:7001/

Finally, Update NGINX

Back on the server update the NGINX configuration with this command:

sudo vi /etc/nginx/sites-available/$SITE.conf

Modify the @django clause to look like this:

location @django {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://localhost:8003;
}

Where the port number in proxy_pass must of course match that in /service/$SITE/run.

sudo nginx -s reload

And Remove uWSGI Configuration

sudo rm /usr/local/etc/uwsgi/emperor.d/$SITE.ini