Gunicorn in Docker
With Docker, our Django project is the only thing running. This means that we don't need gunicorn running in daemon mode and logging to files. We need gunicorn running in the foreground and logging to the console.
Setting up the gunicorn configuration
I found a great article with instructions on using gunicorn in Docker.
In summary, our Docker-specific gunicorn configuration is:
import os
for k, v in os.environ.items():
if k.startswith("GUNICORN_"):
key = k.split('_', 1)[1].lower()
locals()[key] = v
This allows us to specify the gunicorn configuration using environment variables.
Setting up the gunicorn logging
The same article includes instructions about setting up logging. We added json-logging-py==0.2
to requirements.txt
so the required library is included. Chances are, we will be changing all our logging to use this.
We added a docker_gunicorn_conf.py
file to the repo:
[loggers]
keys=root, gunicorn.error
[handlers]
keys=console
[formatters]
keys=json
[logger_root]
level=INFO
handlers=console
[logger_gunicorn.error]
level=ERROR
handlers=console
propagate=0
qualname=gunicorn.error
[handler_console]
class=StreamHandler
formatter=json
args=(sys.stdout, )
[formatter_json]
class=jsonlogging.JSONFormatter
Environment variables
We know that we need to set at least these variables:
GUNICORNCONF
GUNICORN_WORKERS
GUNICORN_BACKLOG
GUNICORN_BIND
GUNICORN_ENABLE_STDIO_INHERITANCE
We will set their values in the Dockerfile
Running gunicorn
Spoiler alert! We will need a script that Docker will run as soon as it starts up the container. We want it to start gunicorn (and do a few other things), so we will create a new script: docker-entrypoint.sh
#!/bin/bash
python $HOMEDIR/manage.py collectstatic --noinput
python $HOMEDIR/manage.py migrate --noinput
#exec newrelic-admin run-program \
gunicorn \
--log-config $HOMEDIR/conf/gunicorn_logging.conf \
--config $HOMEDIR/conf/docker_gunicorn_conf.py \
conf.wsgi:application
We chmod a+x docker-entrypoint.sh
to make it executable.
In case you haven't guessed, $HOMEDIR
is set in the Dockerfile
.
Why are you running the collectstatic
command here? Good question! You should be proud of yourself for the amount of awareness you maintain.
Initially the collectstatic
command was in the Dockerfile
, so it could be a part of the Docker image. However, whether it is by design, because of some of our code, or a third-party app, the command loads all the settings and apps and attempts to connect to the database when it runs. We can't have that.
At least it doesn't take long to run, and doesn't do anything if it has already run.
I'll explain more next time when I cover the Dockerfile
creation.
Why are you running the migrate
command here? Mostly because most of the example configurations for Docker and Django do this and I can't think of a better place to do it. Like the collectstatic
command, it really only needs to be run once for the image, no matter how many machines use it. And also like the collectstatic
command, we don't (necessarily) have a connection to the database to do this.
Also, it is a bit safer. In the worst case, a migration hasn't been applied, and the container will take a little longer to run the first time. In the best case, it doesn't need to do anything and starts up right away.
Next time
We'll conver creating the initial Dockerfile
and testing it out to see if what we have done works.