Blog post:

Deploy Django apps to Dotcloud with django-deployer in 5 minutes

Tags:

Yesterday I posted a video demonstrating how to deploy a Django project to Google App Engine using django-deployer.

Today I’m going to demonstrate how to deploy the same project (Mezzanine, a blogging/CMS platform) to Dotcloud, another platform-as-a-service (PaaS). Watch this short 4.5 min video to see how it works:

How does it work?

What did we just see in the video?

  1. After cloning the skeleton paasbakeoff project with git clone https://github.com/appsembler/paasbakeoff/
  2. We install django-deployer into our virtualenv with pip install django-deployer
  3. We ran a command deployer-init, that simply copies a ‘fabfile.py’ into our project’s directory.
  4. Then we run fab setup which asks us a bunch of questions about our project. Based on the answers, django-deployer creates all the necessary configuration files to deploy our project to Dotcloud.
  5. Once the project is prepped, then we run the dotcloud create myapp command to create the app.
  6. Lastly, we deploy the app to Dotcloud with the command dotcloud push.

That’s it! In less than 5 minutes, we’ve taken a Django project that knows nothing about Dotcloud, made it Dotcloud-ready, and got it running up on Dotcloud!

What files get created?

deploy.yml

Stores all the answers to the questions after running fab setup. (Note: this file is not needed for Dotcloud and can be deleted after running ‘fab setup’)

admin_password: admin2
database: PostgreSQL
django_settings: mywebsite.settings
managepy: mywebsite/manage.py
media_url: /media/
project_name: mywebsite
provider: dotcloud
pyversion: Python2.7
requirements: mywebsite/requirements/project.txt
static_url: /static/

Note: this file is likely to change in the future. Refer to the django-deployer code for the latest specifications.

dotcloud.yml

dotcloud.yml is the configuration file which tells Dotcloud that we’re deploying a Python 2.7 application and want PostgreSQL as the database. It also sets some environment variables like the DJANGO_SETTINGS_MODULE and UTF-8 encoding on the file system.

www:
  type: python
  config:
    python_version: v2.7
  environment:
    DJANGO_SETTINGS_MODULE: mywebsite.settings_dotcloud
    LANG: en_US.UTF-8
    LC_ALL: en_US.UTF-8
db:
  type: postgresql

createdb.py

This script doesn’t what you would expect, creates the database on Dotcloud. Not printed here because it’s very long, but if you want to see what it looks like you can see an example here.

mkadmin.py

This is a small script which simply creates a superuser so we can login to the Django admin.

#!/usr/bin/env python
from wsgi import *
from django.contrib.auth.models import User
u, created = User.objects.get_or_create(username='admin')
if created:
    u.set_password('secret')
    u.is_superuser = True
    u.is_staff = True
    u.save()

mywebsite/settings_dotcloud.py

This is a special settings file that sets up the DATABASES declaration to use the database that Dotcloud provides. It also configures the STATIC_ROOT and MEDIA_ROOT to put static assets and uploaded media in special directories that Dotcloud provides.

You’ll see that wsgi.py references settings_dotcloud instead of the usual settings and we also set it as an environment variable in dotcloud.yml, so that the manage.py script knows about it too.

import json

with open('/home/dotcloud/environment.json') as f:
    env = json.load(f)

from .settings import *

STATIC_ROOT = '/home/dotcloud/volatile/static/'
STATIC_URL = '/static/'

MEDIA_ROOT = '/home/dotcloud/data/media/'
MEDIA_URL = '/media/'

if 'DOTCLOUD_DATA_MYSQL_HOST' in env:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': env['DOTCLOUD_PROJECT'],
            'USER': env['DOTCLOUD_DATA_MYSQL_LOGIN'],
            'PASSWORD': env['DOTCLOUD_DATA_MYSQL_PASSWORD'],
            'HOST': env['DOTCLOUD_DATA_MYSQL_HOST'],
            'PORT': int(env['DOTCLOUD_DATA_MYSQL_PORT']),
        }
    }
elif 'DOTCLOUD_DB_SQL_HOST' in env:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql_psycopg2',
            'NAME': env['DOTCLOUD_PROJECT'],
            'USER': env['DOTCLOUD_DB_SQL_LOGIN'],
            'PASSWORD': env['DOTCLOUD_DB_SQL_PASSWORD'],
            'HOST': env['DOTCLOUD_DB_SQL_HOST'],
            'PORT': int(env['DOTCLOUD_DB_SQL_PORT']),
        }
    }

nginx.conf

In this file we set up the static mappings for our static and media URLs:

location /media/ { root /home/dotcloud/data ; }
location /static/ { root /home/dotcloud/volatile ; }

postinstall

postinstall is a bash script that gets run after the build is finished. Here is where we create the database (with createdb.py), sync the database, create the superuser (with mkadmin.py) and collect the static assets (optionally we could run the manage.py migrate command if we were using South)

#!/bin/sh
python createdb.py
python mywebsite/manage.py syncdb --noinput
python mkadmin.py
mkdir -p /home/dotcloud/data/media /home/dotcloud/volatile/static
python mywebsite/manage.py collectstatic --noinput

requirements.txt

Dotcloud expects there to be a file named requirements.txt in the root of the project directory. Since Mezzanine already provides one, we’ll just make requirements.txt point to that one:

-r mywebsite/requirements/project.txt

wsgi.py

Lastly, we need to tell Dotcloud how to serve our Django app with a wsgi handler:

import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__),'mywebsite')))
os.environ['DJANGO_SETTINGS_MODULE'] = 'mywebsite.settings_dotcloud'

import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

Notice that we had to add ‘mywebsite’ to the Python path with sys.path.insert.

What makes Dotcloud different?

No more free lunch

Almost all of the PaaS providers have a free tier, undoubtedly to gain developer mindshare. But as of April 25, 2013, Dotcloud is discontinuing their free “sandbox” tier and it will only be available as a paid service. There is a silverlining in this announcement in that Dotcloud will be open sourcing a good chunk of their technology such as Docker and Hipache, core components of their hosted service.

Kudos to Dotcloud for giving away their code so we can learn from it. Although I would argue that the reason people are attracted to PaaS in the first place is because they want to let someone else take care of the servers for them. It will be interesting to see what interesting things people come up with using Docker, a runtime for Linux containers (LXC). See Solomon Hyke’s lightning talk from PyCon 2013 about The Future of Linux Containers.

System packages

Dotcloud lets you install system packages. Anything that’s available in the public Ubuntu apt repositories should be available to install in your application container. Simply add ‘systempackages’ to your dotcloud.yml file and define which ones to install. For example, in order to save time installing PIL or lxml, you can just use the pre-compiled packages provided by Ubuntu:

www:
  type: python
  systempackages:
    - python-imaging
    - python-lxml

Configurable nginx.conf

Dotcloud lets you set up your own Nginx config file. You can go to town with mappings and rewrite rules.

SSH just like you can with a VPS

And as with OpenShift, Heroku, Stackato and Elastic Beanstalk, you can SSH into your application container using this command:

$ dotcloud run www

What’s next?

The django-deployer project is still a work-in-progress and there are many other things I’d like to add to it, such as auto-detection of your settings.py, manage.py and requirements.txt files (less typing), and handling configuration for other services like Celery, Redis, Memcached and email sending. What would you like to see?

The code is open source and available on Github if you want to check it out. Contributions welcome! Feel free to report any bugs or feature requests to the Github issue tracker, or just add a comment below.

 

End of post.