We removed our free Sandbox April 25th.
You can read more on our blog.

Django

According to the Django project homepage, it is a “high-level Python Web framework that encourages rapid development and clean, pragmatic design”.

This tutorial will show how to build a minimal Django project from scratch on dotCloud, storing data in a PostgreSQL database.

All the code presented here is also available on GitHub, at http://github.com/dotcloud/django-on-dotcloud.

Directory Structure

To experiment with this tutorial, you can clone the corresponding GitHub repository with the following command:

$ git clone git://github.com/dotcloud/django-on-dotcloud

Or you can do it step by step, by creating and modifying the files as instructed at each step.

If you chose to do it manually, the final directory structure should look like this:

.
├── dotcloud.yml
├── hellodjango/
│   ├── __init__.py
│   ├── manage.py
│   ├── settings.py
│   ├── helloapp/
│   │   ├── __init__.py
│   │   ├── models.py
│   │   ├── tests.py
│   │   └── views.py
│   ├── someotherapp/
│   │   ├── __init__.py
│   │   ├── models.py
│   │   ├── tests.py
│   │   └── views.py
│   └── urls.py
├── static/
├── mkadmin.py
├── nginx.conf
├── postinstall*
├── requirements.txt
└── wsgi.py

Note

This tutorial does not create someotherapp; it is only shown here so you can see where your custom apps should be added.

dotCloud Build File

The dotCloud Build File, dotcloud.yml, describes our stack.

We will start with a single “python” service (we will add the database later). This service allows us to expose a WSGI-compliant web application. Django can do WSGI out of the box, so that’s perfect for us.

dotcloud.yml:

www:
  type: python

The role and syntax of the dotCloud Build File is explained in further detail in the documentation, at build files guide.

Note

If you set an approot for your service in the dotcloud.yml, the wsgi.py, requirements.txt, postinstall, etc. files need to be located under the directory you specified as your approot. It is easier not to specify approot unless you really need it.

Specifying Requirements

A lot of Python projects use a requirements.txt file to list their dependencies. dotCloud detects this file, and if it exists, pip will be used to install the dependencies.

In our case, we just need to add Django to this file.

requirements.txt:

Django==1.3.1

Note

Django 1.4 was released on March 23, 2012. If you want to use Django 1.4, you can specify Django==1.4 in your requirements. We will point out any differences in Django 1.4 throughout this guide.

pip is able to install code from PyPI (just like easy_install); but it can also install code from repositories like Git or Mercurial, as long as they contain a setup.py file. This is very convenient to install new versions of packages automatically without having to publish them on PyPI at each release.

See http://www.pip-installer.org/en/latest/requirement-format.html for details about pip and the format of requirements.txt.

Note

When you specify your requirements you should also specify the version number so that you don’t accidentally upgrade your dependencies, before you are ready.

Django Basic Files

Let’s pretend that our Django project is called hellodjango. We will add the essential Django files to our project. Actually, those files did not come out of nowhere: we just ran the following command to generate them!

$ django-admin.py startproject hellodjango

Note

The rest of the tutorial assumes that your project is in the hellodjango directory. If you’re following those instructions to run your existing Django project on dotCloud, just replace hellodjango with the real name of your project directory, of course.

The files generated by startproject are:

Django 1.3.1:

.
└── hellodjango
    ├── __init__.py
    ├── manage.py
    ├── settings.py
    └── urls.py

Django 1.4:

.
├── hellodjango
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py

Note

The project directory structure recently changed in Django 1.4, it moved manage.py up one directory and they added a wsgi.py file.

Django Templates

There are a couple of different ways to store your templates, you can have one large template directory for all of your templates, or you can have a separate template directory in each app. To keep things simple we will put them all in one template directory located under hellodjango.

# cd into your hellodjango directory first, then make template directory.
$ mkdir -p templates

We also need to make sure we set the TEMPLATE_DIRS variable in our settings.py file so that it knows where to find our templates

TEMPLATE_DIRS = (
    os.path.join(PROJECT_ROOT, 'hellodjango/templates/'),
)

You might have noticed PROJECT_ROOT, this is a little shortcut to make the location dynamic, so we don’t have to hard code the full path. Add this to the top of the settings.py file, to get PROJECT_ROOT.

import os
settings_dir = os.path.dirname(__file__)
PROJECT_ROOT = os.path.abspath(os.path.dirname(settings_dir))

Now that you have that setup, you can place your templates inside of your new template directory.

Django Basic app

Now that we have our simple django project, lets create our first app helloapp. If you move into the hellodjango directory and run this command it will create the app for us.

$ python manage.py startapp helloapp

Django 1.3.x:

This will generate the following files under hellodjango/helloapp/:

hellodjango/helloapp/__init__.py
hellodjango/helloapp/models.py
hellodjango/helloapp/tests.py
hellodjango/helloapp/views.py

Django 1.4:

This will generate the following file next to hellodjango, at the same level as manage.py:

.
├── helloapp
│   ├── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── hellodjango
│   ├── __init__.py
│   ├── settings.py
│   ├── settings.pyc
│   ├── urls.py
│   └── wsgi.py
└── manage.py

We also need to add helloapp to INSTALLED_APPS in our hellodjango/settings.py. It should look like this now.

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
    'helloapp',
)

Simple view

We are going to create a very simple view that will render a template and return the page as a response. Here is what the helloapp/views.py looks like. It imports our helloworld.html template and injects the current_time variable and then renders to html, and returns the response.

helloapp/views.py:

from django.template import Context, loader
from datetime import datetime
from django.http import HttpResponse

def hello_view(request):
    """ Simple Hello World View """
    t = loader.get_template('helloworld.html')
    c = Context({
        'current_time': datetime.now(),
    })
    return HttpResponse(t.render(c))

This is what our template looks like. Pretty simple.

hellodjango/templates/helloworld.html:

<html>
<head><title>Hello World!</title>
<body>
    <p>Hello World. The current day and time is {{current_time|date:"DATETIME_FORMAT"}}.</p>
</body>
</html>

Before we can use it, we need to add the view to our urls.py file so that we can link our url to our view. This is what it looks like when we are done. If someone goes to the root of the domain it will show the result of hello_view.

hellodjango/urls.py:

from helloapp.views import hello_view

urlpatterns = patterns('',
    url(r'^$', view=hello_view, name='hello_page'),
)

wsgi.py

The wsgi.py file will bridge between the python service and our Django app.

We need three things here:

  • append our hellodjango directory to our python sys path.
  • inject the DJANGO_SETTINGS_MODULE variable into the environment, pointing to our project settings module;
  • setup the application callable, since that is what the dotCloud service will be looking for.

wsgi.py:

import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__),'hellodjango')))
os.environ['DJANGO_SETTINGS_MODULE'] = 'hellodjango.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

Note

Django 1.4 now comes with a wsgi.py file under your project. This is a different one that needs to be at the root of your project, next to your dotcloud.yml file.

We can now create our application the flavor of our choice and push our application, by running the following commands:

$ dotcloud create djangotutorial
Created application "djangotutorial" using the flavor "sandbox" (Use for development, free and unlimited apps. DO NOT use for production.)
$ dotcloud push djangotutorial

(you can of course use any application name you like). A Python service will be created, the code will be deployed, and the URL of the service will be shown at the end of the build. If you go to this URL, you will see the plain and boring Django page, typical of the “just started” project.

Add PostgreSQL Database

It’s time to add a database to our project! The first step is to tell dotCloud that we want a PostgreSQL server to be added to our application. Just edit the Build File again.

dotcloud.yml:

www:
  type: python
db:
  type: postgresql

Note that we called our database db, but it could have been anything else, really.

If you dotcloud push again, you will see that the database service will be created (dotCloud will notice that we added a section to the Build File).

Creating your Database

When you create a new database service, you will be given a raw database server with full root access. It will be up to you to create the database and users that you want for your application. Normally you would need to add the database service to your dotcloud.yml file, push the service. Let the service start up and then login to create your database.

Here is what that looks like, if you had a mysql service.

# connect to dotcloud mysql server instance
$ dotcloud run djangotutorial.db -- mysql -u root -p

# mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 34
Server version: 5.1.41-3ubuntu12.10 (Ubuntu)

# create the user and database and give user permissions to database.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> create database happydb;
Query OK, 1 row affected (0.00 sec)

mysql> create user 'db_username' identified by 'strong_password';
Query OK, 0 rows affected (0.05 sec)

mysql> grant all on happydb.* to 'db_username'@'%';
Query OK, 0 rows affected (0.04 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

mysql> exit;Bye
Shared connection to database closed.

That is the long way, now I’ll show you the short way. You can create a script that will run during the postinstall process, and see if the database has been created or not, and if not, it will create it. All other times, it does nothing. The script will parse the information from your settings.py DATABASES values. This script only works with MySQL and PostgreSQL, but can be easily changed to support other databases.

Here is what createdb.py looks like:

import os
import time

import MySQLdb
import psycopg2
import _mysql_exceptions
from wsgi import *

def create_dbs():
    deadline = time.time() + 60
    while time.time() < deadline:
        try:
            print("create_dbs: let's go.")
            django_settings = __import__(os.environ['DJANGO_SETTINGS_MODULE'], fromlist='DATABASES')
            print("create_dbs: got settings.")
            databases = django_settings.DATABASES
            for name, db in databases.iteritems():
                host = db['HOST']
                user = db['USER']
                password = db['PASSWORD']
                port = db['PORT']
                db_name = db['NAME']
                db_type = db['ENGINE']
                # see if it is mysql
                if db_type.endswith('mysql'):
                    print 'creating database %s on %s' % (db_name, host)
                    db = MySQLdb.connect(user=user,
                                        passwd=password,
                                        host=host,
                                        port=port)
                    cur = db.cursor()
                    print("Check if database is already there.")
                    cur.execute("""SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA
                                 WHERE SCHEMA_NAME = %s""", (db_name,))
                    results = cur.fetchone()
                    if not results:
                        print("Database %s doesn't exist, lets create it." % db_name)
                        sql = """CREATE DATABASE IF NOT EXISTS %s """ % (db_name,)
                        print("> %s" % sql)
                        cur.execute(sql)
                        print(".....")
                    else:
                        print("database already exists, moving on to next step.")
                    exit(0)
                # see if it is postgresql
                elif db_type.endswith('postgresql_psycopg2'):
                    print 'creating database %s on %s' % (db_name, host)
                    con = psycopg2.connect(host=host, user=user, password=password, port=port, database='postgres')
                    con.set_isolation_level(0)
                    cur = con.cursor()
                    try:
                        cur.execute('CREATE DATABASE %s' % db_name)
                    except psycopg2.ProgrammingError as detail:
                        print detail
                        print 'moving right along...'
                    exit(0)
                else:
                    print("ERROR: {0} is not supported by this script, you will need to create your database by hand.".format(db_type))
                    exit(1)
        except psycopg2.OperationalError:
            print "Could not connect to database. Waiting a little bit."
            time.sleep(10)
        except _mysql_exceptions.OperationalError:
            print "Could not connect to database. Waiting a little bit."
            time.sleep(10)


    print 'Could not connect to database after 1 minutes. Something is wrong.'
    exit(1)

if __name__ == '__main__':
    import sys
    print("create_dbs start")
    create_dbs()
    print("create_dbs all done")

Add Database Credentials to settings.py

Now, we need to edit settings.py to specify the host, port, user, and password to connect to our database. When you deploy your application, these parameters are stored in the dotCloud Environment File. This allows you to repeat the deployment of your application (e.g. for staging purposes) without having to manually copy-paste the parameters into your settings each time.

If you don’t want to use the Environment File, you can retrieve the same information with dotcloud info hellodjango.db.

The Environment File is a JSON file holding a lot of information about our stack. It contains (among other things) our database connection parameters. We will load this file, and use those parameters in Django’s settings.

See dotCloud environment guide for more details about the Environment File.

hellodjango/settings.py:

# Django settings for hellodjango project.

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

DEBUG = True
TEMPLATE_DEBUG = DEBUG
# …
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'happydb',
        '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']),
    }
}
# …

Note

We decided to use the name happydb here. You can call it what ever you want. If you use the createdb.py script, it will look for this database name, and if it isn’t created, it will create it for you. If you don’t use the createdb.py script you will need to create the database yourself.

Django Admin Site

We will now activate the Django administration application. Nothing is specific to dotCloud here: we just uncomment the relevant lines of code in settings.py and urls.py.

hellodjango/settings.py:

# …
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
    'helloapp',
)
# …

hellodjango/urls.py:

from django.conf.urls.defaults import patterns, include, url
# import the view from helloapp
from helloapp.views import hello_view
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    url(r'^$', view=hello_view, name='hello_page'),
    url(r'^admin/', include(admin.site.urls)),
)

If we push our application now, we can go to the /admin URL, but since we didn’t call syncdb yet, the database structure doesn’t exist, and Django will refuse to do anything useful for us.

Automatically Call syncdb

To make sure that the database structure is properly created, we want to call manage.py syncdb automatically each time we push our code. On the first push, this will create the Django tables; later, it will create new tables that might be required by new models you will define.

To make that happen, we create a postinstall script. It is called automatically at the end of each push operation.

postinstall:

#!/bin/sh
python createdb.py
python hellodjango/manage.py syncdb --noinput

A few remarks:

  • this is a shell script (hence the #!/bin/sh shebang at the beginning), but you can also use a Python script if you like;
  • The createdb.py script is first so that it creates the database before we try to syncdb
  • by default, syncdb will interactively prompt you to create a Django superuser in the database, but we cannot interact with the terminal during the push process, so we disable this thanks to --noinput.
  • make sure that you have set the correct execute permissions on all files that you plan to run with your postinstall script including the postinstall script.

Database Migrations

This project is too small, and doesn’t require database migrations, but if you are using a database migration tool like Django South you can add the following to your postinstall script to run your migrations at the correct time. For more information about Django south visit this website: http://south.aeracode.org

python hellodjango/manage.py migrate

If you push the code at this point, hitting the /admin URL will display the login form, but we don’t have a valid user yet, and the login form won’t have the usual Django CSS since we didn’t take care about the static assets yet.

Create Django Superuser

Since the syncdb command was run non-interactively, it did not prompt us to create a superuser, and therefore, we don’t have a user to login.

To create an admin user automatically, we will write a simple Python script that will use Django’s environment, load the authentication models, create a User object, set a password, and give him superuser privileges.

The user login will be admin, and its password will be password. Note that if the user already exists, it won’t be touched. However, if it does not exist, it will be re-created. If you don’t like this admin user, you should not delete it (it would be re-added each time you push your code) but just remove its privileges and reset its password, for instance.

mkadmin.py:

#!/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('password')
    u.is_superuser = True
    u.is_staff = True
    u.save()

postinstall:

#!/bin/sh
python createdb.py
python hellodjango/manage.py syncdb --noinput
python mkadmin.py

At this point, if we push the code, we will be able to login, but we still lack the CSS that will make the admin site look nicer.

Handle Static and Media Assets

We still lack the CSS required to make our admin interface look nice. We need to do three things here.

First, we will edit settings.py to specify STATIC_ROOT, STATIC_URL, MEDIA_ROOT, and MEDIA_URL.

MEDIA_ROOT will point to /home/dotcloud/data. By convention, the data directory will persist across pushes. This is important: You don’t want to store media (user uploaded files...) in current or code, because those directories are wiped out at each push.

We decided to point STATIC_ROOT to /home/dotcloud/volatile, since the static files are “generated” at each push. We could have put them in current but to avoid conflicts and confusions we chose a separate directory.

Your application will need a place to store your static files, so we are going to put them in a directory called static at the same level as hellodjango. In order for Django to know that there is static media there, we need to tell it, by adding that directory to STATICFILES_DIRS in settings.py.

hellodjango/settings.py:

# …
# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = '/home/dotcloud/data/media/'

# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
MEDIA_URL = '/media/'

# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/home/media/media.lawrence.com/static/"
STATIC_ROOT = '/home/dotcloud/volatile/static/'

# URL prefix for static files.
# Example: "http://media.lawrence.com/static/"
STATIC_URL = '/static/'

# Additional locations of static files
STATICFILES_DIRS = (
    os.path.join(PROJECT_ROOT, 'static/'),
)

# …

The next step is to instruct Nginx to map /media to that directory in /home/dotcloud/data and /static to that directory in /home/dotcloud/volatile. This is done through a Nginx configuration snippet. You can do many interesting things with custom Nginx configuration files; the dotCloud nginx guide gives some details about that.

nginx.conf:

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

Warning

The python service no longer sets up the /static/ directory for us. You must define your static location in nginx.conf as of August 2012.

If you want to store your static media in a different directory that is fine, you just need to make the appropriate changes in the django settings.py.

The last step is to add the collectstatic management command to our postinstall script. Before calling it, we create the required directories, just in case.

postinstall:

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

After pushing this last round of modifications, the CSS for the admin site (and other static assets) will be found correctly, and we have a very basic (but functional) Django project to build on!

Note

If you are using collectstatic make sure you have it enabled in your INSTALLED_APPS in settings.py.

Django Logging

Setting up your application to use Django logging is pretty easy, you just need to make some minor changes to your settings.py. This change below will put your Django logs in /var/log/supervisor/helloapp.log.

# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(message)s'
        },
    },
    'handlers': {
        'null': {
            'level':'DEBUG',
            'class':'django.utils.log.NullHandler',
        },
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'verbose'
        },
        'log_file': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',
            'formatter': 'verbose',
            'filename': '/var/log/supervisor/helloapp.log',
            'maxBytes': 1024*1024*25, # 25 MB
            'backupCount': 5,
        },
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler'
        }
    },
    'loggers': {
        'django': {
            'handlers': ['console', 'log_file', 'mail_admins'],
            'level': 'INFO',
            'propagate': True,
        },
        'django.request': {
            'handlers': ['console', 'log_file', 'mail_admins'],
            'level': 'ERROR',
            'propagate': False,
        },
        'django.db.backends': {
            'handlers': ['console', 'log_file', 'mail_admins'],
            'level': 'INFO',
            'propagate': False,
        },
        # Catch All Logger -- Captures any other logging
        '': {
            'handlers': ['console', 'log_file', 'mail_admins'],
            'level': 'INFO',
            'propagate': True,
        }
    }
}

Logs

If you need to look at the logs to see how it is going you can do it two ways. The first way will tail your logs for you to your console.

# look at logs of your service, it will tail them to your console. ctrl-c to stop.
$ dotcloud logs djangotutorial.www

Or login via ssh and look at your logs.

# Open up a shell
$ dotcloud ssh djangotutorial.www

Here are the ones you most likely care about.

# nginx access and error logs.
/var/log/nginx/<app_name>.{access,error}.log

# wsgi error logs
/var/log/supervisor/uswgi.log

# Django app log we setup above.
/var/log/supervisor/helloapp.log

Caching

If your application needs to add caching, you can use the redis service.

To add a redis service, you just need to add it to the dotcloud.yml file.

dotcloud.yml:

www:
  type: python
db:
  type: postgresql
cache:
  type: redis

Django doesn’t support using redis as a cache out of the box. So you will need to add a third party Django application called django-redis.

To install django-redis you will need to add django-redis to your requirements.txt file.

requirements.txt:

Django==1.3.1
django-redis==1.4.5

Then add to the following code to the settings.py:

CACHES = {
    'default': {
        'BACKEND': 'redis_cache.cache.RedisCache',
        'LOCATION': env['DOTCLOUD_CACHE_REDIS_HOST']+':'+env['DOTCLOUD_CACHE_REDIS_PORT'],
        'OPTIONS': {
            'DB': 1,
            'PASSWORD': env['DOTCLOUD_CACHE_REDIS_PASSWORD'],
            'PARSER_CLASS': 'redis.connection.HiredisParser'
        },
    },
}

# we also are going to use redis for our session cache as well.
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'

Once you have made all of those changes and pushed them to dotCloud, your service should be ready to use. Feel free to play around with the services to see how they work. ssh into the services and find your way around, and if you have any issues or questions, don’t hesitate to contact support.

Extra Tips

Here are some extra tips that will help you as you progress past the simple tutorial and onto more advanced topics.

Error Pages

There are a couple of different error pages that you need to configure if you want to cover all of your bases.

Nginx

The first kind of error pages are the ones served by Nginx if it has a problem connecting to your application or if it can’t find a file it needs to serve. There are 5 different error pages that are configured with the default Nginx config. One error page for each of the following HTTP error codes: 404,500,502,503 and 504. You will need to create simple error pages named {error_code}.html and place them in the static directory. Here is a list of the files you will need.

  • 404.html
  • 500.html
  • 502.html
  • 503.html
  • 504.html

Now you will need to add the following to your nginx.conf, so nginx knows where to find those files.

nginx.conf:

# make sure you have your /static location block declared above this

error_page 404 = /static/404.html;
error_page 500 = /static/500.html;
error_page 502 = /static/502.html;
error_page 503 = /static/503.html;
error_page 504 = /static/504.html;

Django

The other kind of error pages are the ones that are served by Django itself. These error pages usually have the same look and feel as the rest of your site. For more information about configuring these error pages see the following links.

robots.txt and favicon.ico

If you want to add robots.txt and favicon.ico files to your app, you can put them in the``static`` directory. Nginx will do the rest.

Scheduled tasks

If your app needs to run scheduled tasks, you can use cron jobs for this. Follow the steps in the dotCloud periodic tasks guide.

It is important to remember that when you are creating a cron job you need to specify the fully qualified path for everything. Take a look at the these two examples, one way is what not to do, the other is the right way.

BAD:

42 18 * * * python manage.py my_awesome_custom_command

GOOD:

42 18 * * * /home/dotcloud/env/bin/python /home/dotcloud/current/myapp/manage.py my_awesome_custom_command

If you are going to scale your application, you will need to be careful that you only have your periodic tasks running on one service at a time. The recommended way to handle this is to have a service just for your periodic tasks that isn’t scaled. Or to use a service like django-celery which has periodic task support.

If you want to have just one service that also handles cron, you can add the following to your postinstall script. It will look at which service instance it is installing on, and if it is on the first one, do something, if not on the first one, it skips that step. It does this by looking at the hostname of the service.

Note: This assumes your service is named www if your service is named something different, you will need to change accordingly.

#!/bin/sh
echo "HOSTNAME=$(cat /etc/hostname)"
case "$(cat /etc/hostname)" in
*-www-0)
echo "We are on www-0; executing specific management commands..."
# you can install cron here, or anything else you just want to run on one service instance at a time.
;;
*)
echo "We are not on www-0; skipping specific management commands..."
exit 0
;;
esac

Background tasks

This demo app doesn’t really have a need for celery, but dotCloud does support it. For more information look at the dotCloud django-celery guide

File Uploads

dotCloud allows your visitors to upload files to your website, but the data won’t be available for long term storage. If you need to have access to these files in the future it is recommended that you store them somewhere else.

The most popular location to store your files is Amazon’s S3 service. If you want to store your files on Amazon S3 from you Django app then you should look at a project like django-queued-storage that will store your files onto S3 for you.

Another thing you can do is create an S3FS mount on your service that gives you direct access to your S3 bucket from your file system. See the dotCloud S3FS guide for details on how to setup your S3FS service.

S3FS

If you store data on s3 you can mount your s3 bucket so that you can have access to s3 from your application, just like it was a local directory on your container. This is helpful for storing files that are uploaded by your visitors, or to share files between different web processes. Follow these instructions on the dotCloud S3FS guide to set it up.

Email

If you want to send email from your application, then look at these two guides they will help you figure out what you need.

Backups

You should always remember to backup any data that you can’t easily regenerate. The most important data to backup is your database. Luckily dotCloud makes it easy to back up your database. There is a very helpful dotCloud backup guide on how to setup your database backups.

Scaling

If you plan on running a production application, it is recommended that you scale your application accordingly. If you need help figuring out how to scale your application you should reference the dotCloud scaling guide.