Dockerfile for quickly running Django projects in a Docker container.
Run Django projects from source using Gunicorn and Nginx.
There are a few ways that your Django project needs to be set up in order to be compatible with this Docker image.
setup.py
Your project must have a setup.py
. All dependencies (including Django itself) need to be listed as install_requires
.
Static files
Your project's static files must be set up as follows:
STATIC_URL = '/static/'
STATIC_ROOT
=BASE_DIR/static
orBASE_DIR/staticfiles
Media files
If your project makes use of user-uploaded media files, it must be set up as follows:
MEDIA_URL = '/media/'
MEDIA_ROOT
=BASE_DIR/media
orBASE_DIR/mediafiles
Note: Any files stored in directories called static
, staticfiles
, media
, or mediafiles
in the project root directory will be served by Nginx. Do not store anything here that you do not want the world to see.
In the root of the repo for your Django project, add a Dockerfile for the project. For example, this file could contain:
FROM praekeltfoundation/django-bootstrap
ENV DJANGO_SETTINGS_MODULE "my_django_project.settings"
RUN django-admin collectstatic --noinput
ENV APP_MODULE "my_django_project.wsgi:application"
Let's go through these lines one-by-one:
- The
FROM
instruction here tells us which image to base this image on. We use thedjango-bootstrap
base image. - We set the
DJANGO_SETTINGS_MODULE
environment variable so that Django knows where to find its settings. This is necessary for anydjango-admin
commands to work. - Optional: If you need to run any build-time tasks, such as collecting static assets, now's the time to do that.
- We set the
APP_MODULE
environment variable that will be passed togunicorn
, which is installed and run in thedjango-bootstrap
base image.gunicorn
needs to know which WSGI application to run.
The django-bootstrap
base image actually does a few steps automatically using Docker's ONBUILD
instruction. It will:
COPY . /app
- copies the source of your project into the imageRUN chown -R gunicorn:gunicorn /app
- ensures thegunicorn
user can write to/app
and its subdirectoriesRUN pip install -e .
- installs your project usingpip
All these instructions occur directly after theFROM
instruction in your Dockerfile.
By default, the django-entrypoint.sh
script is run when the container is started. This script runs a once-off django-admin migrate
to update the database schemas and then launches nginx
and gunicorn
to run the application.
This django-entrypoint.sh
script also allows you to create a Django super user account if needed. Setting the SUPERUSER_PASSWORD
environment variable will result in a Django superuser account being made with the admin
username. This will only happen if no admin
user exists.
You can skip the execution of this script and run other commands by overriding the CMD
instruction. For example, to run a Celery worker, add the following to your Dockerfile:
CMD ["celery", "worker", \
"--app", "my_django_project", \
"--loglevel", "info"]
Alternatively, you can override the command at runtime:
docker run my_django_project_image celery worker --app my_django_project --loglevel info
Add a file called .dockerignore
to the root of your project. A good start is just to copy in the .dockerignore
file from the example Django project in this repo.
This image automatically copies in the entire source of your project, but some of those files probably aren't needed inside the Docker image you're building. We tell Docker about those unneeded files using a .dockerignore
file, much like how one would tell Git not to track files using a .gitignore
file.
As a general rule, you should list all the files in your .gitignore
in your .dockerignore
file. If you don't need it in Git, you shouldn't need it in Docker.
Additionally, you shouldn't need any Git stuff inside your Docker image. It's especially important to have Docker ignore the .git
directory because every Git operation you perform will result in files changing in that directory (whether you end up in the same state in Git as you were previously or not). This could result in unnecessary invalidation of Docker's cached image layers.
NOTE: Unlike .gitignore
files, .dockerignore
files do not apply recursively to subdirectories. So, for example, while the entry *.pyc
in a .gitignore
file will cause Git to ignore ./abc.pyc
and ./def/ghi.pyc
, in a .dockerignore
file, that entry will cause Docker to ignore only ./abc.pyc
. This is very unfortunate. In order to get the same behaviour from a .dockerignore
file, you need to add an extra leading **/
glob pattern — i.e. **/*.pyc
. For more information on the .dockerignore
file syntax, see the Docker documentation.
It's common for Django applications to have Celery workers performing tasks alongside the actual website. In most cases it makes sense to run each Celery process in a container separate from the Django/Gunicorn one, so as to follow the rule of one(-ish) process per container. But in some cases, running a whole bunch of containers for a relatively simple site may be overkill. Additional containers generally have some overhead in terms of CPU and, especially, memory usage.
This image provides the option to run a Celery worker inside the container, alongside Gunicorn/Nginx. To run a Celery worker you must set the CELERY_APP
environment variable.
Note that, as with Django, your project needs to specify Celery in its install_requires
in order to use Celery. Celery is not installed in this image by default.
The following environment variables can be used to configure Celery. A number of these can also be configured via the Django project's settings.
- Required: yes
- Default: none
- Celery option:
-A
/--app
- Required: no
- Default: none
- Celery option:
-b
/--broker
- Required: no
- Default:
INFO
- Celery option:
-l
/--loglevel
Note that by default Celery runs as many worker processes as there are processors. We instead default to 1 worker process here to ensure containers use a consistent and small amount of resources. If you need to run many worker processes, they should be in separate containers.
- Required: no
- Default: 1
- Celery option:
-c
/--concurrency
Set this option to any non-empty value to have a Celery beat scheduler process run as well.
- Required: no
- Default: none
- Celery option: n/a
Gunicorn is run with some basic configuration:
- Runs WSGI app defined in
APP_MODULE
environment variable - Starts workers under the
gunicorn
user and group - Listens on a Unix socket at
/var/run/gunicorn/gunicorn.sock
- Access logs can be logged to stderr by setting the
GUNICORN_ACCESS_LOGS
environment variable to a non-empty value.
Extra settings can be provided by overriding the CMD
instruction to pass extra parameters to the entrypoint script. For example:
CMD ["django-entrypoint.sh", "--threads", "5", "--timeout", "50"]
See all the settings available for gunicorn here. A common setting is the number of Gunicorn workers which can be set with the WEB_CONCURRENCY
environment variable.
Nginx is set up with mostly default config:
- Access logs are sent to stdout, error logs to stderr
- Listens on port 8000 (and this port is exposed in the Dockerfile)
- Serves files from
/static/
and/media/
- All other requests are proxied to the Gunicorn socket
Generally you shouldn't need to adjust Nginx's settings. If you do, the configuration files of interest are at:
/etc/nginx/nginx.conf
: Main configuration/etc/nginx/conf.d/django.conf
: Proxy configuration