Django in Docker
Django project using Docker
Pre-requisites and assumptions
- Using Django 1.7, Python 3.3, Docker 1.5, docker-compose 1.5
- Assume that python, setuptools, installed
- Assume Docker and docker-compose installed
Basic setup
First we need to have a Django project that we can build and run in Docker.
django-admin startproject django_demo
cd django_demo
./manage.py startapp blog
This will provide us a base project and site to expand upon using Docker.
Reference structure
Reference structure at a glance.
tree -I *.pyc
.
├── blog
│ ├── admin.py
│ ├── __init__.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── django_demo
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── docker-compose.yml
├── Dockerfile
├── manage.py
├── requirements.txt
└── run_web.sh
requirements.txt
Add the Python packages we need for the demo.
django>=1.7,<1.8
psycopg2
gunicorn
dj-static==0.0.6
run_web.sh
A run script that is convenient for development and production.
We do the collectstatic here and not in the Dockerfile so you only have to restart the container to update any static files. I have seen people putting static files in the image build process.
#!/bin/bash
##
# Version: 1.1
# Author: jeffreyrobertbean@gmail.com
# Date: 3/21/2015
##
# Simple retry function
function retry {
local n=1
local max=5
local delay=10
while true; do
"$@" && break || {
if [[ ${n} -lt ${max} ]]; then
((n++))
echo "Migration failed. Attempt $n/$max:"
sleep ${delay};
else
echo "The command has failed after $n attempts."
exit 1
fi
}
done
}
## Validate the django project is going to load properly (does not mean it will run)
python manage.py validate
###
# migrate db, so we have the latest db schema
#
# Need to retry so if postgres is starting for the first time.
# Also allows time to see any errors before it attempts to start the server.
##
retry python manage.py migrate --noinput
## Collect all the static files.
python manage.py collectstatic --noinput
##
# start server on the docker ip interface, port 8001
# Also handles if we are going to start the production gunicorn server or the develop server
##
if [ -z ${DJANGO_DEBUG_MODE} ]; then
su -m djuser -c "gunicorn django_demo.wsgi -w 1 -b 0.0.0.0:8001 --chdir=/code --enable-stdio-inheritance --error-logfile -"
else
su -m djuser -c "python manage.py runserver 0.0.0.0:8001"
fi
Dockerfile
FROM python:3.3
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/
CMD ./run_web.sh
docker-compose.yml
You dont want to build your own postgres image so lets pick the offical postgres image. Here we pick 9.4 to make sure the demo works.
The following docker-compose
file here is used for DEVELOPMENT.
db:
image: postgres:9.4
web:
build: .
ports:
- "8000:8001"
links:
- db
volumes:
- .:/code
environment:
- DJANGO_DEBUG_MODE=True
docker-compose.production.yml
The following docker-compose
file here is used for PRODUCTION.
You can see here it is assumed that your built image is now either on the Docker Hub or a private registry. This will run the server in the production settings using gunicorn to be the server for the django app.
db:
image: postgres:9.4
web:
image: <your_django_docker_image>
ports:
- "8000:8001"
links:
- db
django_demo/settings.py
The settings.py needs to be updated to work nicely with the Docker DB container that will be running. To do this we use the environment to fill in our conection info.
We also need to add our new blog app to the INSTALLED_APPS
confguration variable.
"""
Django settings for django_demo project.
For more information on this file, see
https://docs.djangoproject.com/en/1.7/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.7/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'somethingdemosecretkey'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.getenv('DJANGO_DEBUG_MODE', False)
TEMPLATE_DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
ROOT_URLCONF = 'django_demo.urls'
WSGI_APPLICATION = 'django_demo.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': os.environ.get('DB_ENV_DB', 'postgres'),
'USER': os.environ.get('DB_ENV_POSTGRES_USER', 'postgres'),
'PASSWORD': os.environ.get('DB_ENV_POSTGRES_PASSWORD', ''),
'HOST': os.environ.get('DB_PORT_5432_TCP_ADDR', ''),
'PORT': os.environ.get('DB_PORT_5432_TCP_PORT', ''),
},
}
# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
STATIC_ROOT = os.path.join(BASE_DIR, 'cstatic')
STATIC_URL = '/static/'
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
django_demo/wsgi.py
We want to use a simple static file server that will serve out static files from the Django app. The wsgi settings need to modified to do this. This is not recommended for production applications but for the purpose of this demo it makes things a little easier.
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_demo.settings")
from django.core.wsgi import get_wsgi_application
from dj_static import Cling
application = Cling(get_wsgi_application())
Build
docker-compose build
docker-compose pull
docker-compose run web ./manage.py makemigrations
docker-compose up
Finish
Now you can navigate to http://localhost:8000 or if you are using boot2docker the IP address of the VM:8000.
Congrats! you are running Django in Docker.
Summary
This touturial gets a nice Django project up and running in Docker.
Next post will be making a Blog application on top of this infrastructure.