:title: Django Python Tutorial :description: How to build a Django project from scratch on dotCloud, storing data in a PostgreSQL database. :keywords: dotCloud, tutorial, documentation, python, Django, PostgreSQL Django ====== .. include:: ../../dotcloud2note.inc 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. .. contents:: :local: :depth: 1 Directory Structure ------------------- To experiment with this tutorial, you can clone the corresponding GitHub repository with the following command: .. code-block:: bash 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``: .. code-block:: yaml www: type: python The role and syntax of the dotCloud Build File is explained in further detail in the documentation, at :doc:`Build File 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! .. code-block:: bash 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``. .. code-block:: bash # 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 .. code-block:: python 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``. .. code-block:: python 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. .. code-block:: bash 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. .. code-block:: python 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``: .. code-block:: python 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``: .. code-block:: html Hello World!

Hello World. The current day and time is {{current_time|date:"DATETIME_FORMAT"}}.

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``: .. code-block:: python 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``: .. code-block:: python 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 with the :doc:`flavor ` of our choice and push our application, by running the following commands: .. code-block:: bash dotcloud create djangotutorial dotcloud push (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``: .. code-block:: yaml 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 Postgresql service. .. code-block:: bash # connect to dotcloud mysql server instance dotcloud run db -- psql You can then type the following to create a database, user, and grant the user access to the newly created database:: create database happydb; create user db_username with password 'strong_password'; grant all privileges on database happydb to db_username; \q Here is what that looks like, if you had a MySQL service. .. code-block:: bash # connect to dotcloud mysql server instance dotcloud run db -- mysql You can then type the following to create a database, user, and grant the user access to the newly created database:: create database happydb; create user 'db_username' identified by 'strong_password'; grant all on happydb.* to 'db_username'@'%'; flush privileges; exit; 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: .. code-block:: python 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 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 :doc:`dotCloud environment guide ` for more details about the Environment File. ``hellodjango/settings.py``: .. code-block:: python # 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``: .. code-block:: python # … 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``: .. code-block:: python 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``: .. code-block:: sh #!/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 .. code-block:: sh 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``: .. code-block:: python #!/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``: .. code-block:: sh #!/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 conflict and confusion 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``: .. code-block:: python # … # 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 :doc:`dotCloud nginx guide ` gives some details about that. ``nginx.conf``: .. code-block:: nginx 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``: .. code-block:: sh #!/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``. .. code-block:: python # 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. .. code-block:: sh # look at logs of your service, it will tail them to your console. ctrl-c to stop. dotcloud logs www Or login via ssh and look at your logs. .. code-block:: sh # Open up a shell dotcloud run www Here are the ones you most likely care about. .. code-block:: sh # nginx access and error logs. /var/log/nginx/.{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``: .. code-block:: yaml 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``: .. code-block:: python 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 when it has a problem connecting to your application or if it can't find the 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``: .. code-block:: nginx # 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. - https://docs.djangoproject.com/en/1.4/topics/http/urls/#error-handling - https://docs.djangoproject.com/en/1.4/topics/http/views/#customizing-error-views 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 :doc:`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**: .. code-block:: bash 42 18 * * * python manage.py my_awesome_custom_command **GOOD**: .. code-block:: bash 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 it accordingly. .. code-block:: bash #!/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 :doc:`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. 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. - :doc:`dotCloud email guide ` - :doc:`dotCloud smtp service ` 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 :doc:`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 :doc:`dotCloud scaling guide `.