A standard stack for Django, using python packaging and Fabric for single-line deployments on Debian/Ubuntu machines.
A "bundle" is like an app on ep.io, or an instance on gondor.io. You can deploy as many bundles as you want on a single machine.
This isn't intended for large-scale deployment but rather small sites fitting on a single server (although you can scale vertically).
Almost everything here is implemented, a couple of things are still missing:
- Bundle destruction
- Python (duh)
- PostgreSQL
- Redis (RQ tasks, cache backend)
- Gunicorn
- Supervisor
- Nginx
- Sentry, using a remote sentry server
- GIS-ready by default
- HTTPS handling with A grade from ssllabs.com
- XSendfile support
- Package your django project, you should be able to pip install it from a
private location. Your package should contain base default settings that
fab-bundle
will extend, for instance inproject/default_settings.py
. - Put your private requirements (if any) into a
vendor/
directory, as python packages.
pip install ssh pip install https://github.com/brutasse/fab-bundle/tarball/master#egg=fab-bundle
Create a fabfile.py
file in your project root:
from fab_bundle import env, task, bootstrap, deploy, destroy, ssh @task def production(): """Use the production server""" # SSH login info env.user = 'bruno' env.hosts = ['example.com'] env.key_filename = '/path/to/id_rsa' env.admin = '[email protected]' # Nginx env.http_host = 'foo.example.com' # Django env.base_settings = 'project.default_settings' env.secret_key = 'your private secret confidential key'
Bootstrap the server setup:
fab production bootstrap
Deploy your package:
fab production deploy
This runs setup.py sdist
, uploads the package and its private requirements
to the server and updates or creates the bundle's environment and layout.
For subsequent deploys you don't need to run bootstrap
again, although
doing so is harmless.
To deploy a specific version (for instance for rolling back), add your version number as an argument:
fab production deploy:1.1.2
Note that this will not re-upload the package if it's already been uploaded.
Should you ever need a plain shell, do:
fab production ssh
You need to add the following packages to your environment:
- django-redis-cache
- psycopg2
- redis
Every day you get an email with the load average, out-of-date packages and
disk space available on your machine. This email is sent to env.admin
:
env.admin = '[email protected]'
Fab-bundle checks for the presence of ssl_key
and ssl_cert
in
env
:
env.ssl_cert = '/path/to/ssl_cert.crt' env.ssl_key = '/path/to/ssl_cert_key.key'
Just set them to local files on your machine and your site will be configured to be HTTPS-only, with:
- HSTS support
- Secure session and CSRF cookies
- Permanent redirection from non-SSL to SSL requests
- HTTPS on static and media serving
Bundles are put in $HOME/bundles
by default. To change this, set
bundle_root
:
def production(): # ... env.bundle_root = '/var/www/bundles'
You can use Sentry in remote mode, by adding this to the env
object:
def production(): # ... env.sentry_dsn = 'you sentry DSN'
Make sure your project itself is configured to use raven
.
def production(): # ... env.email = { 'from': 'Example <[email protected]>', 'host': 'smtp.example.com', 'user': 'example', 'password': 'yay', }
You can also set the 'tls'
, 'port'
and 'backend'
keys.
Fab-bundle will try to install postgres 9.1. If it's not available on your system, you'll need to check which version you have, make sure you pick the one that works with postgis as well:
apt-cache search postgis
This outputs stuff like postgresql-8.4-postgis
. Then set:
env.pg_version = '8.4'
You will get daily DB backups in $HOME/dbs
, they're kept for 7 days and
then rotated, so it's up to you to back them up offsite if you need to.
Only Nashvegas is currently supported.
def production(): # ... env.migrations = 'nashvegas'
Note that you need to provide the path to your migrations in
NASHVEGAS_MIGRATIONS_DIRECTORY
, for instance in your base settings:
NASHVEGAS_MIGRATIONS_DIRECTORY = os.path.join( os.path.abspath(os.path.dirname(__file__)), 'migrations', )
They're enabled by default. To disable them:
def production(): # ... env.staticfiles = False
To add scheduled tasks:
def production(): # ... env.cron = ( ('*/30 * * * *', './env/bin/django-admin.py command_name --settings=settings'), )
Commands are run from your bundle root. This folder contains:
- the virtualenv in
env/
- the nginx, supervisor, etc config in
conf/
- the nginx, supervisor and gunicorn logs in
log/
- the static and media files in
public/
- the settings and wsgi files,
settings.py
andwsgi.py
- the python packages in
packages/
If you have your own PyPI for deployments, you can point to it like this:
def production(): # ... env.index_url = 'https://login:[email protected]/index'
Note that it will be passed to pip's --index-url
argument, not
--find-links
or --extra-index-url
so you need all your dependencies
here.
RQ support is opt-in:
def production(): # ... env.rq = True
You still need to specify the python requirements yourself.
If you need custom settings that are only suited to your production
environment, set them as a string in env.settings
:
from textwrap import dedent def production(): # ... env.settings = dedent(""" REGISTRATION_OPEN = True """).strip()
Make sure there is no indentation, the code must be valid top-level python code. Custom settings are appended to the default ones.
If you have several bundles on the same server and they use cache, you may want to specify the ID of the redis DB to use:
env.cache = 1
Nginx has the ability to serve private files and leave your upstream server decide whether the file should be served or not via a header. This is called XSendfile
To make this work with fab-bundle, set env.xsendfile to the list of locations you want to protect:
env.xsendfile = [ '/media/private/', '/media/other/', ]
Note that your MEDIA_ROOT
is served under the /media/
URL prefix.
Then in your view:
response = HttpResponse(mimetype='application/octet-stream') response['X-Accel-Redirect'] = '/media/private/file-one.zip' return response
Fab-bundle installs the libraries required by geodjango and creates all the
databases from a spatial template. If you don't need this, you can disable GIS
support by setting env.gis
:
env.gis = False
Had a bad deploy? It happens. Rollback to a previous version, let's say 1.2:
fab production deploy:1.2
Databases are dumped every day, you can sync them as well as your media files using a script such as:
#/ /bin/sh RSYNC="rsync -avz -e ssh" $RSYNC <host>:dbs . $RSYNC <host>:bundles/<http-domain>/public/media . mkdir -p log $RSYNC <host>:bundles/<http-domain>/log/*.gz log
Want to remove your app? This will remove everything related to your bundle:
fab production destroy