Heroku buildpack: Plone
This is a Heroku buildpack for Plone apps, powered by zc.buildout. You can use the free-tier Heroku account to run a simple, production-quality, Plone site, absolutely for free, forever.
Plone on Heroku
To run Plone on Heroku, you need to add the following heroku.cfg
buildout configuration to your
repository (assuming you already have a buildout.cfg
in your repository:
[buildout]
extends = buildout.cfg
relative-paths = true
[instance]
relative-paths = true
eggs +=
RelStorage
psycopg2
rel-storage =
keep-history false
blob-dir /tmp/blobcache
shared-blob-dir false
type postgresql
host PG_HOST
dbname PG_DBNAME
user PG_USER
password PG_PASS
To understand the configuration changes, you need to know the following things about Heroku:
-
Heroku refers to a single server instance as a dyno.
-
Heroku uses a two-step deployment workflow. In Plone's case, this buildpack first compiles a "runtime slug" with
bin/buildout
. Then, Heroku copies the slug to a dyno and runs your app code against this slug withbin/instance console
. Becausebin/buildout
runs in a different place frombin/instance
, we need to fix some of the paths that buildout generates. Therelative-paths
flag fixes most of the paths for us, and then the buildpack usessed
to fix the rest of the paths in the buildout-generatedzope.conf
file. -
At
bin/buildout
slug compile-time, the port number on which Plone will run is unknown. The port number is given as an environment variable only when Heroku runs the runtime slug on a dyno. Because Plone does not support environment variables in thezope.conf
and does not read command-line parameters whenbin/instance
starts, this buildpack includes aconfigure_zopeconf.py
script that injects port number from the environment variables intozope.conf
. This script runs just before starting the Plone instance on the dyno. -
Heroku uses an ephemeral filesystem, which discards all data when you restart the dyno. However, Heroku provides world-class Postgres-as-a-service persistency options. To use this service, you need to configure Plone to use the
RelStorage
add-on, which enables support for relational DB. Similar to the port number, theconfigure_zopeconf.py
script injects the DB connection settings from the environment variables intozope.conf
. -
If you use a free-tier account, Heroku gives you one dyno instance per month. However, if your site has no traffic for longer than one hour, Heroku puts the dyno to sleep. If the dyno is asleep when the next request is made to your site, there will be some delay before a response is sent back (~20s for Heroku startup, ~20s for Plone startup). Subsequent requests will be served at a normal speed.
-
The free-tier PostgreSQL DB included in this buildpack has a limit of 10k rows. To stay below this limit, the
heroku.cfg
sets thekeep-history false
flag to discard transactional history. This flag disables theUndo
Plone feature, but allows you to retain document history, keeps your DB small and manageable, and eliminates the need to regularly "pack" your DB. Based on preliminary tests, the 10k limit is reached after creating about 200 content items. After that, you need to move to the first paid PostgreSQL plan ($9/month) that increases the limit to 10 million rows. To check how many rows your DB uses, runheroku pg:info
.
Usage
This section shows an example prompt session. This example assumes that you already signed up for a Heroku account and successfully installed the Heroku Toolbelt:
$ ls
buildout.cfg heroku.cfg
$ git add heroku.cfg
$ git commit -m "add support for deploying on Heroku"
$ heroku create --buildpack git://github.com/plone/heroku-buildpack-plone.git
$ git push heroku master
...
-----> Plone app detected
-----> Use build cache
Get buildout results from the previous build
-----> Bootstrap buildout
...
-----> Run bin/buildout
...
-----> Fix paths in zope.conf
-----> Copy results to cache
-----> Copy results to slug
-----> Copy configure_zopeconf.py script to slug
-----> Discovering process types
Procfile declares types -> (none)
Default types for Plone -> web
$ heroku open
$ heroku logs -t
The buildpack will detect that your app is a Plone app if your repo has the file buildout.cfg
in the root. Heroku will use zc.buildout
to install your dependencies and "vendor" a copy of the Plone runtime into your slug. The buildpack will define a default Procfile
so you don't have to manually create it. The buildpack will also provision a free-tier PostgreSQL DB for persistence.
If you want general information about Heroku, you can read how Heroku works.
Options
Running an arbitrary *.cfg file
To run an arbitrary *.cfg file such as production.cfg
instead of the default heroku.cfg
, set the following environment variable:
$ heroku config:add BUILDOUT_CFG=production.cfg
Increase buildout verbosity
To increase the verbosity of bin/buildout
, set the following environment variable:
$ heroku config:add BUILDOUT_VERBOSITY=-v
You can increase verbosity up to -vvvv
.
Use arbitrary bootstrap.py
If you want to use an arbitrary bootstrap.py
file, for example to enable support for zc.buildout
2.x, set the following environment variable:
$ heroku config:add BOOTSTRAP_PY_URL=http://downloads.buildout.org/2/bootstrap.py
Creating a demo for your Plone package
This buildpack allows you to easily create a demo of your Plone package for users to try the package out without the hassle of installing the package locally for themselves:
- Add
heroku.cfg
to your package's repo, based on the sampleheroku.cfg
provided in this repo. - Add your package's specific configuration to the
heroku.cfg
file. - For a demo installation we want the DB to reset every once in a while, so visitors get a fresh install of Plone + your package, without content that other visitors created. This means, we want to go back to using
Data.fs
filestorage DB that will get removed every time the dyno running your demo is restarted. To go back to filestorage DB, just remove therel-storage
section from yourheroku.cfg
file. - Follow instructions in the
Usage
section of this README to deploy Plone, along with your package, to Heroku. - Optionally, extend the
heroku.cfg
with support for creating a pre-populated Plone site instance with buildout.
If you get stuck, see how it's done in collective.cover and plone.app.mosaic.
Migrating an existing Plone site to Heroku
When moving to Heroku, the main task is to migrate from a ZODB filesystem DB to a PostgreSQL relational DB. Migrations might differ, but generally you follow these steps:
- Download
Data.fs
& BLOBs to your local machine. - Add the Heroku-specific configuration to the local
heroku.cfg
file. - Run
git push heroku master
to deploy your code to Heroku and create a PostgreSQL DB. - Run
heroku config
and write down theDATABASE_URL
DB connection string. - Follow the instructions on https://pypi.python.org/pypi/RelStorage#id18 to complete the migration.
A sample zodbconvert.conf
:
<filestorage source>
path var/filestorage/Data.fs
blob-dir var/blobstorage
</filestorage>
<relstorage destination>
shared-blob-dir false
blob-dir ./var/blobcache
blob-cache-size 10mb
keep-history false
<postgresql>
dsn dbname='<DATABASE>' user='<USER>' host='<HOST>' password='PASSWOR'
</postgresql>
</relstorage>
Additional resources
- Blog post introducing Plone support on Heroku: http://www.niteoweb.com/blog/dear-plone-welcome-to-2014
- About Buildpacks: https://devcenter.heroku.com/articles/buildpacks
- About custom Buildpacks: https://devcenter.heroku.com/articles/buildpack-api
✨ Bonus Karma Points ✨
The following tips n' tricks will help you make the most out of the Heroku environment.
DNS & Cloudflare
By default, your app will be available on <APP_NAME>.herokuapp.com
. You can set a custom domain even with the free-tier account:
$ heroku domains:add www.mydomain.com
Then you create a CNAME
record that points to <APP_NAME>.herokuapp.com
. More info on https://devcenter.heroku.com/articles/custom-domains.
It's recommended to use Cloudflare as a DNS provider. Their free-tier plan acts as a simple CDN, while their AlwaysOnline feature helps when your free dyno goes to sleep.
Virtual hosting
By default, your Plone site will be available at <DOMAIN>/Plone
. But normally, you want to have your Plone site served at root (/
). No worries, Zope's Virtual Hosting Monster to the rescue!
Go to <DOMAIN>/Plone/virtual_hosting/manage_edit
and add the following entry:
<DOMAIN>/Plone
Click Save Changes
and reload.
Sending emails
The Heroku dyno does not include sendmail
. Even if it did, it is not recommended to send emails from an unknown server/IP if you want them to be delivered. Instead, provision the Mailgun add-on that gives you a free SMTP server for outgoing email, along with tracking delivery/open rates:
$ heroku addons:add mailgun
Then navigate to the MailGun control panel to add and configure a domain for your app. Optionally, you can enable tracking of email delivery & clicks.
After your domain is ready, go to /@@mail-controlpanel
on your Plone site and enter hostname/credentials from Mailgun.
Log archival
Heroku stores 1500 most recent logs entries that you can browse through with the heroko logs
command. To keep a longer history of logs, provision the free-tier level of the Papertrail add-on to get 7 days of searchable log history plus the ability to store unlimited daily archives to your personal Amazon S3 account:
$ heroku addons:add papertrail
DB backups
To allow its users a good night's sleep, Heroku offers daily DB backups. Let's enable them:
$ heroku addons:add pgbackups:auto-month
As an exercise, the extra-paranoid among us can also configure offsite DB backups to an Amazon S3 account.
More Heroku add-ons
Browse through all available add-ons and see if any of them catches your attention.