Fork me on GitHub

PaaS Cheatsheet

Quick start guide to deploying apps to PaaS providers

Introduction

This project grew out of my observation that each PaaS provider requires you to configure your project in a special way in order to use their particular service.

Let's say I want to use a MySQL database with a PaaS provider. Rather than digging through their online documentation and code examples, trying to remember how a particular PaaS provider handles the creation and configuration of a MySQL database, I wanted a reference guide that would show me quickly how they do it.

Thus the PaaS cheatsheet was born.

It's currently Django specific as that's my web framework of choice these days, but you can fork the project and add docs for other frameworks such as Rails, Node.js, PHP, Java, etc. Contributions are most welcome! Also see the contribute section below for more info.

Feel free to contact me at @natea on Twitter or on Github.

You can also submit a ticket in the Github issue tracker or comment in the comments box below.

Background

I've been exploring various PaaS providers to see which ones are suitable for integration with Appsembler, a platform that I've been building which SaaS-enables open source web applications.

Appsembler's mission is to make it easier to choose open source web software. Leveraging PaaS, we can more easily offer open source web apps in the SaaS model, and skip the download-configure-deploy dance. And by offering these apps in the SaaS model, it means they can be used by a wider audience - not just technical users.

I wrote a series of blog posts about my experience deploying a Django app to various PaaS providers, and then gave a talk at the local Boston Django users group and also at PyCon 2013. Here are some links to those blog posts and talks in case you're interested in reading more.

Contribute

As mentioned in the introduction, the PaaS Cheatsheet is on Github and you are encouraged to fork it, and add other frameworks or PaaS providers. Just submit a pull request!

In addition to this project, there are two sister projects:

  • django-deployer is an attempt to create a universal PaaS agnostic deployer tool for Django projects.
  • paasbakeoff contains a skeleton project for Mezzanine, a popular Django-based CMS that lives on the master branch. There are separate branches with example config files to deploy Mezzanine to each PaaS provider.

paasbakeoff is more of a reference repo for testing purposes, but if you'd like to help out with django-deployer, please let me know!

Getting Started

Click a tab to see how to get started with a particular PaaS provider.

Heroku

Download and install the Heroku Toolbelt.

Login, create the app and deploy it with these commands:

          
$ heroku login
$ heroku create myapp
$ git push heroku master
        
        

SSH into the running instance

          
$ heroku run bash
          
        

Dotcloud

Install the dotcloud command line tool with pip.

          
$ sudo pip install dotcloud
        
        

Login, create the app and deploy it with these commands:

          
$ dotcloud login
$ dotcloud create myapp
$ dotcloud push
        
        

SSH into the running instance

          
$ dotcloud run www
          
        

OpenShift

Install the rhc command line tool with gem.

          
$ sudo gem install rhc
        
        

Login, create the app and deploy it with these commands:

          
$ rhc setup
$ rhc create -a myapp -t python-2.6
$ git push
        
        

SSH into a "gear"

When you create an application, your gear is created with a user that runs your application's stack as well as owns your application code in the cloud. To access that application gear with that user, you need to run:
          
$ rhc app ssh -a {appName}
          
        

For scaled applications, the above will get you access to your HAProxy gear.

To get access to your framework gear, SSH onto your HAProxy gear, then review the contents of ~/haproxy-1.4/conf/gear-registry.db:

          
$ more ~/haproxy-1.4/conf/gear-registry.db
c7e82cd61276415e801c16ffbde81d78@10.32.51.100:c7e82cd612;c7e82cd612-macdomain.rhcloud.com
          
        

Then SSH onto one of those gears on the list (note that the IP address may change, so it's best to use the DNS c7e82cd612-macdomain.rhcloud.com):

                  
$ ssh c7e82cd61276415e801c16ffbde81d78@c7e82cd612-macdomain.rhcloud.com
          
        

To get access to your DB gear (MySQL in this case), SSH into your HAProxy gear and run this:

           
$ echo $OPENSHIFT_MYSQL_DB_GEAR_UUID@$OPENSHIFT_MYSQL_DB_GEAR_DNS
        
        

Then from either your rhc client machine, or your HAProxy gear, you can run:

           
$ ssh {$OPENSHIFT_MYSQL_DB_GEAR_UUID}@{$OPENSHIFT_MYSQL_DB_GEAR_DNS}
          
        

Elastic Beanstalk

Download and install the AWS Elastic Beanstalk Command Line tool.

          
$ wget https://s3.amazonaws.com/elasticbeanstalk/cli/AWS-ElasticBeanstalk-CLI-2.3.1.zip
$ unzip AWS-ElasticBeanstalk-CLI-2.3.1.zip
$ export PATH=$PATH:[path to unzipped EB CLI package]/eb/linux/python2.7/
$ alias eb="python2.7 [path to unzipped EB CLI package]/eb/linux/python2.7/eb"
          
          

Create a local git repository and initialize the app:

          
$ git init
$ eb init
Enter your AWS Access Key ID (current value is "AKIAIOSFODNN7EXAMPLE"): 
Enter your AWS Secret Access Key (current value is "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"): 
Enter an AWS Elastic Beanstalk application name (auto-generated value is "windows"): djangoapp
Enter an AWS Elastic Beanstalk environment name (current value is "djangoapp-env"): 
Create an RDS DB Instance? [y/n]: y
Enter an RDS DB master password: *******
Retype password to confirm: *******

        
        

Start and deploy the app:

          
$ eb start
$ eb status --verbose
$ git aws.push
          
        

Requirements

Heroku

Add the following to your requirements.txt file:

          
Django==1.5
South==0.7.6
psycopg2==2.4.6

# for parsing the DATABASE_URL string
dj-database-url==0.2.1

# for storing static assets on Amazon S3
django-storages==1.1.6
boto==2.8.0

# needed for gunicorn (gunicorn, gevent, greenlet)
gunicorn==0.17.2
gevent==0.13.7
greenlet==0.4.0
        
        

Dotcloud

Add the following lines to your requirements.txt file:

          
Django==1.5
South==0.7.6
psycopg2==2.4.6        # if you're using PostgreSQL
MySQL-python==1.2.4    # if you're using MySQL
        
        

OpenShift

Add the following lines to your requirements.txt file:

          
Django==1.5
South==0.7.6
psycopg2==2.4.6        # if you're using PostgreSQL
MySQL-python==1.2.4    # if you're using MySQL
        
        

setup.py

Then create a setup.py file with this line to read the requirements.txt file:

          
install_requires=open('%s/requirements.txt' % os.environ.get('OPENSHIFT_REPO_DIR', PROJECT_ROOT)).readlines(),
          
        

Elastic Beanstalk

Add the following lines to your requirements.txt file:

          
Django==1.4.1
South==0.7.6
MySQL-python==1.2.3
        
        

Database settings

Create the database

          
$ heroku addons:add heroku-postgresql:dev
          
        

Add to settings.py

          
import dj_database_url

DATABASES = {'default': dj_database_url.config(default='postgres://localhost')}
        
        

Add to dotcloud.yml

For PostgreSQL:

          
db:
  type: postgresql
          
        

For MySQL:

          
db:
  type: mysql
          
        

Add to settings.py

          
import json

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

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']),
      }
  }
        
        

Create database

For PostgreSQL:

          
$ rhc cartridge add -c postgresql-8.4 -a [myapp]
          
        

For MySQL:

          
$ rhc app cartridge add -c mysql-5.1 -a [myapp]
          
        

Add to settings.py

          
import os
import urlparse

DATABASES = {}
if 'OPENSHIFT_MYSQL_DB_URL' in os.environ:
    url = urlparse.urlparse(os.environ.get('OPENSHIFT_MYSQL_DB_URL'))

    DATABASES['default'] = {
        'ENGINE' : 'django.db.backends.mysql',
        'NAME': os.environ['OPENSHIFT_APP_NAME'],
        'USER': url.username,
        'PASSWORD': url.password,
        'HOST': url.hostname,
        'PORT': url.port,
        }
elif 'OPENSHIFT_POSTGRESQL_DB_URL' in os.environ:
    url = urlparse.urlparse(os.environ.get('OPENSHIFT_POSTGRESQL_DB_URL'))

    DATABASES['default'] = {
        'ENGINE' : 'django.db.backends.postgresql_psycopg2',
        'NAME': os.environ['OPENSHIFT_APP_NAME'],
        'USER': url.username,
        'PASSWORD': url.password,
        'HOST': url.hostname,
        'PORT': url.port,
        }
        
        

Create database

Read the article Using Amazon RDS with Python.

          
$ eb init
...
Create an RDS DB Instance? [y/n]:
Enter an RDS DB master password: 
Retype password to confirm:
          
        

settings.py

          
import os

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': os.environ['RDS_DB_NAME'],
        'USER': os.environ['RDS_USERNAME'],
        'PASSWORD': os.environ['RDS_PASSWORD'],
        'HOST': os.environ['RDS_HOSTNAME'],
        'PORT': os.environ['RDS_PORT'],
    }
}
        
        

Environment Variables

Adding Environment Variables

          
$ heroku config:add \
    STORAGE=s3storage \
    S3_ACCESS_KEY=[Your AWS Access Key] \
    S3_SECRET_KEY=[Your AWS Secret Key] \
    S3_BUCKET_NAME=[Name for an S3 Bucket] \
    REDIS_URI=[Redis URI from OpenRedis] \
    DB_URI=[Postgres URI from Heroku] \
    PASSCODE=[Your Super Secret Passcode]
        
        

Adding Environment Variables

          
$ dotcloud env set \
    'AWS_ACCESS_KEY=IA4F0njNcmKKg3YndpOe' \
    'AWS_SECRET_KEY=Ideeghu0Ohghe7oi?Y6ogh7qui%jeiph7yai[coo'
        
        

Or in the dotcloud.yml

You don't need to do both. Either set it with 'env set' or add them to the dotcloud.yml file.

Warning! you should not put sensitive information in the dotcloud.yml file, if this file is being pushed up to a public repository.
          
www:
  type: python
  environment:
    MODE: production
    AWS_ACCESS_KEY=IA4F0njNcmKKg3YndpOe
    AWS_SECRET_KEY=Ideeghu0Ohghe7oi?Y6ogh7qui%jeiph7yai[coo
        
        

OpenShift exposes many environment variables and you can specify custom environment variables using the following:

SSH into your application and navigate to your data directory.

          
cd app-root/data
          
        

In this directory create a file with your variables (e.g. ".myenv") with content like:

          
export MY_VAR="something"
          
        

And then in your repository in ".openshift/action_hooks/pre_start" add this line:

          
source ${OPENSHIFT_DATA_DIR}/.myenv
        
        

.ebextensions directory

Create an .ebextensions directory in the top-level directory of your source bundle.

          
$ mkdir .ebextensions
          
        

Create a configuration file with the extension .config (e.g., myapp.config) and place it in the .ebextensions top-level directory. For more information, see the Customizing and Configuring a Python Container

.ebextensions/myapp.config

          
container_commands:
  01_syncdb:    
    command: "django-admin.py syncdb --noinput"
    leader_only: true

option_settings:
  - namespace: aws:elasticbeanstalk:container:python
    option_name: WSGIPath
    value: mywebsite/wsgi.py
  - option_name: DJANGO_SETTINGS_MODULE
    value: mywebsite.settings
  - option_name: AWS_SECRET_KEY
    value: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
  - option_name: AWS_ACCESS_KEY_ID
    value: AKIAIOSFODNN7EXAMPLE
        
        
Warning! you should not put sensitive information in the myapp.config file, if this file is being pushed up to a public repository.

WSGI Handler

Procfile

Heroku uses a Procfile. Here's a sample one:

          
web: gunicorn_django -b 0.0.0.0:\$PORT -w 9 -k gevent --max-requests 250 --preload path/to/settings.py
        
        

Or you can run gunicorn standalone (where you have a file mywebsite/wsgi.py):

          
web: gunicorn -b 0.0.0.0:$PORT -k gevent -w 4 mywebsite.wsgi:application
        
        

wsgi.py

          
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'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()        
        
Warning! You must insert any directories that must be on the Python path using sys.path.insert. In this example, we're inserting the directory 'mywebsite' which contains our Django project.

wsgi/application

Create a directory called 'wsgi' and inside it, put a file 'application':

          
#!/usr/bin/env python

import os
import sys

sys.path.append(os.path.join(os.environ['OPENSHIFT_REPO_DIR']))
os.environ['DJANGO_SETTINGS_MODULE'] = 'mywebsite.settings'
virtenv = os.environ['OPENSHIFT_HOMEDIR'] + 'python-2.6/virtenv/'
os.environ['PYTHON_EGG_CACHE'] = os.path.join(virtenv, 'lib/python2.6/site-packages')
virtualenv = os.path.join(virtenv, 'bin/activate_this.py')

try:    
    execfile(virtualenv, dict(__file__=virtualenv))
except IOError:    
    pass

## IMPORTANT: Put any additional includes below this line.  If placed above this
# line, it's possible required libraries won't be in your searchable path

import django.core.handlers.wsgi

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

.ebextensions/myapp.config

          
option_settings:
  "aws:elasticbeanstalk:application:environment":
    DJANGO_SETTINGS_MODULE: "djproject.settings"
    "application_stage": "staging"
  "aws:elasticbeanstalk:container:python":
    WSGIPath: mywebsite/wsgi.py
    NumProcesses: 3
    NumThreads: 20
        
        

wsgi.py

          
import os

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mywebsite.settings")

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
        
        

Static files

Heroku

Heroku doesn't provide a native way to serve up static assets, so you must push your static assets to another provider such as Amazon S3.

STATIC_ROOT

In settings.py:

          
STATICFILES_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
        
        

MEDIA_ROOT

In settings.py

          
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
        
        

django-storages

In requirements.txt

          
django-storages
        
        

In requirements.txt

          
INSTALLED_APPS += ("storages",)
          
        

Amazon S3 configuration

In settings.py

          
AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "")
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", "")
AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_STORAGE_BUCKET_NAME", "")

STATIC_URL = 'http://' + AWS_STORAGE_BUCKET_NAME + '.s3.amazonaws.com/'
MEDIA_URL = STATIC_URL + 'media/'
          
        

Dotcloud

Dotcloud lets you serve up static assets via Nginx, and persists uploaded media across deploys.

STATIC_ROOT

In settings.py:

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

In nginx.conf:

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

MEDIA_ROOT

In settings.py:

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

Add another line to your nginx.conf:

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

OpenShift

OpenShift also provides a directory that gets persisted across deploys, and static mapping using an .htaccess file:

STATIC_ROOT

in settings.py:

          
STATIC_ROOT = os.path.join(os.environ.get('OPENSHIFT_REPO_DIR'), 'wsgi', 'static')
        
        

In wsgi/static/.htaccess:

          
RewriteEngine On
RewriteRule ^application/static/(.+)$ /static/$1 [L]
        
        

MEDIA_ROOT

OpenShift provides a persisted data dir that can be referenced with the environment variable OPENSHIFT_DATA_DIR:

in settings.py:

          
MEDIA_ROOT = os.path.join(os.environ.get('OPENSHIFT_DATA_DIR'), 'media')
        
        

You then need to symlink this directory to the static directory that is being served up by Apache (see above in STATIC_ROOT).

In .openshift/action_hooks/build:

          
#!/bin/bash
if [ ! -d $OPENSHIFT_DATA_DIR/media ]; then
    mkdir $OPENSHIFT_DATA_DIR/media
fi
 
ln -sf $OPENSHIFT_DATA_DIR/media $OPENSHIFT_REPO_DIR/wsgi/static/media
        
        

Elastic Beanstalk

.ebextensions/myapp.config

          
option_settings:
  ...
  "aws:elasticbeanstalk:container:python:staticfiles":
    "/static/": "static/"
        
        

Logging

Coming soon! Contributions welcome :)

Scaling

Coming soon! Contributions welcome :)

Backups

Coming soon! Contributions welcome :)

Custom domains

Coming soon! Contributions welcome :)

Email

Coming soon! Contributions welcome :)

SSL / HTTPS

Coming soon! Contributions welcome :)

System packages

Coming soon! Contributions welcome :)

Background jobs

Coming soon! Contributions welcome :)

comments powered by Disqus