Installing WordPress and HHVM on Heroku For Beginners

For someone not familiar with Heroku, it can be a bit daunting to get WordPress and HHVM running on a Heroku web dyno after working on a traditional LAMP stack. That’s why I titled this for beginners because I are one and it took me a while to wrap my head around it. Let me also say that I am not a Heroku master and this tutorial most certainly will be agnostic of some of the more technical aspects of Heroku.

This tutorial is also just a means to get WordPress running on a single dyno (server) using the free tier and has not been tested on an enterprise installation. As a point-of-reference, though technically savvy, I do not use https://github.com/mchung/heroku-buildpack-wordpress because the template they use has actual distribution code committed to the repo which relies on a human to continually update. At this reading, some of the plugins are out-of-date, and I prefer to pull distributions from the source using Composer.

There are a few considerations that need to be addressed before starting. Heroku dynos are not equipped with MySQL or a mail server, so we must employ cloud-based services (Heroku add-ons) like ClearDB and Postmark. Also, the uploads folder should be considered as ephemeral (disposable) which means we must employ AWS S3 for storing uploads. By separating these layers, scaling your application with more dynos becomes quite simple.

Install Heroku Toolbelt and Composer

Although not necessary, it is a good idea to install the Heroku Toolbelt. This will allow you to interact with your application from the command line. Heroku also makes good use of Composer which is a bit of a black box at first. In order to generate a composer.lock file that is used in the git-to-deploy process, you will need to install Composer.

Create The App

Go to the Heroku dashboard and create a new application.
Screen Shot 2015-01-15 at 8.31.46 PM

Click the Code tab to view local installation instructions. Here’s the long and short of it:

  • Login to Heroku Toolbelt
    heroku login
  • Create a local directory and initialize a git repo
    mkdir your-app-name
    cd your-app-name
    git init
    
  • Add the git remote via Toolbelt
    heroku git:remote -a your-app-name
  • Or, add the git remote via git
    git remote add heroku git@heroku.com:your-app-name.git
  • Use git normally for committing changes to your app
    git add .
    git commit -am "Initial commit"
    
  • To deploy, push to heroku master
    git push heroku master

Add .gitignore

Here is a list of folders I ignore on my Heroku projects:

#Replacement for wp-content
content
#WordPress
app
#Composer dependencies
vendor

If you wish to add a custom theme to the content directory simply use the following:

git add -f content/themes/my-custom-theme

Add composer.json

I won’t go into the intricacies of using Composer. However, the following is a sample composer.json file that should be placed in the root of your application. By default, packagist.org is set as a repository. I have also included the WordPress Composer repository at wpackagist.org. You can browse packages at either repository and include them in your application.

Example composer.json

{
	"name": "your-app-name",
	"description": "This is a description of your app",
	"version": "0.1.0",
	"type": "root",
	"keywords": [],
	"homepage": "http://www.example.com",
	"minimum-stability": "dev",
	"repositories": [
		{ "type": "composer", "url": "http://wpackagist.org" }
	],
	"require": {
		"hhvm":"~3.3",
		"johnpbloch/wordpress": "4.1",
		"johnpbloch/wordpress-core-installer": "~0.1",
		"composer/installers": "dev-master",
		"wpackagist-plugin/akismet": "~3.0.4",
		"wpackagist-plugin/amazon-s3-and-cloudfront": "~0.8",
		"wpackagist-plugin/amazon-web-services": "~0.2.1",
		"wpackagist-plugin/postmark-approved-wordpress-plugin": "~1.6"
	},
	"extra": {
	    "wordpress-install-dir": "app",
	    "installer-paths":{
	    	"content/plugins/{$name}/": ["type:wordpress-plugin"]
	    }
	}
}

The packages of note with regards to WordPress are the following:

  • johnpbloch/wordpress

    This is a forked repository of the official WordPress git mirror that includes composer.json files which make each release and branch of WordPress a Composer package

  • johnpbloch/wordpress-core-installer

    Composer, by default, installs all packages in the vendor/ directory of your application. This package allows WordPress to be installed in a custom directory. Note extra.wordpress-install-dir

  • composer/installers

    Like the previously noted package, this is an official installer for Composer that supports WordPress themes and plugins (Composer package type wordpress-plugin). Any package included from wpackagist.org can be installed in a custom path. By default, plugins/themes will be installed in wp-content/*. Note extra.installer-paths

Add Procfile

By default, Heroku will detect which process to run for the environment. You technically don’t need a Procfile, but it gives you explicit control over your app by defining it as a web dyno running Apache. Simply add a file named Procfile with the following contents:

web: vendor/bin/heroku-hhvm-apache2

Add Postmark Add-on

We need to create a mail server, and my favorite is Postmark. Click the Resources tab of your application, then click ‘Get more add-ons’. Once you’ve installed the free tier, you will need to visit the configuration page to create an application for the API token and a verified sender signature. Note the official Postmark WordPress plugin in composer.json. You will find the settings page under Settings->Postmark in WordPress. There are simple instructions to get this working properly.

Add ClearDB Add-on

Like the previous add-on, install the free tier of ClearDB. Small sites usually fit within the free tier (5mb). The connection string is stored as a PHP environment variable (getenv(‘CLEARDB_DATABASE_URL’)) which keeps our configuration out of the codebase. Here is an example of a wp-config.php implementation. Note, you can also view the configuration variables in the Settings tab of your application.

$db = getenv("CLEARDB_DATABASE_URL");
$url = parse_url($db);
define("DB_NAME", trim($url["path"], "/"));
define("DB_USER", trim($url["user"]));
define("DB_PASSWORD", trim($url["pass"]));
define("DB_HOST", trim($url["host"]));

As an aside, if you have a small site, the free tier will work just fine. I recommend disabling revisions and setting auto save in WordPress to a higher interval so as not to unnecessarily add bytes to your database. Set the following in your wp-config.php file:

define('WP_POST_REVISIONS', false );
define('AUTOSAVE_INTERVAL', 500 );

I also disable all comments and pingbacks as well in the Settings->Discussion admin page. Individual posts can override the global settings, so if you’ve migrated your site from somewhere else, you will need to run the following DML in SequelPro or phpMyAdmin to globally nuke those settings:

UPDATE wp_posts SET comment_status='closed';
UPDATE wp_posts SET ping_status='closed';

Generate composer.lock

In a normal composer setup, you would ignore the lock file, but Heroku will not proceed with a build until it detects a composer.lock file. This ‘locks’ down the versions of dependencies. To generate this file run the following and commit composer.lock:

composer install

After the initial install use the following to regenerate composer.lock if you’ve made any changes to composer.json. The –ignore-platform-reqs flag will allow Composer to update without choking on the hhmv package if it is not locally installed. If you have it installed on your local machine, you can ignore the ignore flag.

composer update --ignore-platform-reqs

Create S3 Bucket

One of the greatest things about Amazon Web Services is that you pay pennies per month for storing a small WordPress uploads folder on S3. This tutorial does not even attempt to be authoritative on AWS configuration. The following is a simple tutorial, but I suggest you take some more time to read their documentation and learn more about IAM credentials, user policies, bucket policies, etc…

To create a bucket, click the Services menu item and go to S3. Once you’ve created your bucket, click the bucket name and select the Properties tab.

Screen Shot 2015-01-16 at 7.59.14 AM

You need to make the bucket publicly readable. Under the permissions tab, select Edit Bucket Policy and paste in the following policy.

{
	"Version": "2015-01-15",
	"Statement": [
		{
			"Sid": "AllowPublicRead",
			"Effect": "Allow",
			"Principal": {
				"AWS": "*"
			},
			"Action": "s3:GetObject",
			"Resource": "arn:aws:s3:::yourbucketname/*"
		}
	]
}

From there, you need to create a web user that will be able to access the S3 bucket via the API. To do that, click the Services menu item and go to IAM. Click the users tab and create a new user. Once the user has been created, you will be asked to download the credentials. DO IT! You will not be able to do this again without regenerating the key/secret. Store these credentials in a place you will remember.

Now, select the user and then click ‘Attach User Policy’. Select the template titled ‘Amazon S3 Full Access’. If you select Manage Policy, you will see something that looks like this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": "*"
    }
  ]
}

Now, you’re ready to configure WordPress! You need to add two configuration variables to Heroku: AWS_S3_KEY and AWS_S3_SECRET. You can do this one of two ways. If you have the Toolbelt installed, simply type:

heroko config:set AWS_S3_KEY=%ENTER_YOUR_IAM_KEY_HERE%

If you don’t have the Toolbelt installed, click the Settings tab in your Heroku app and add them vars manually.
Screen Shot 2015-01-16 at 8.13.50 AM

To connect this to WordPress, simply add the following to your wp-config.php file:

define( 'AWS_ACCESS_KEY_ID', getenv('AWS_S3_KEY') );
define( 'AWS_SECRET_ACCESS_KEY', getenv('AWS_S3_SECRET'));

Activate both AWS plugins as noted in composer.json and follow the instructions under the WordPress menu, AWS->S3 and Cloudfront.

If you have existing attachments, you will need to manually upload them to your S3 bucket either through a client or the AWS console. From there, you need to generate some postmeta for the attachments for the S3 plugin to recognize that you’ve synced them with your S3 bucket. I’ve created a small utility plugin for this. To use it, simply activate it, and then navigate to: http://yourdomain.com/app/wp-admin/?s3=1&bucket=your-bucket-name

<?php
/*
Plugin Name: Coderrr S3 Conversion
Description: Converts previously uploaded attachments to work with amazon-s3-and-cloudfront plugin
Version: 0.1
Author: Brian Fegter
License: GPLv2+
*/

/**
 * Converts previous attachment metadata to work with amcn-s3-uploads plugin
 * Usage when logged in as an admin:
 * 	http://example.com/wp-admin/?s3=1&bucket=mybucket
 */

class Coderrr_S3_Conversion{

	/**
	 * Obscure Query Variable
	 * @var string
	 */
	private $key = 's3';

	/**
	 * Password
	 * @var string
	 */
	private $pass = '1';

	/**
	 * Constructor Method
	 */
	public function __construct(){
		#Set a high priority so all admin assets load
		add_action('admin_init', array($this, 'migrate_data'), 9999999);
	}

	/**
	 * Verifies the authority of the user as well as the authenticity of the request
	 * Checks if:
	 * 	- Is wp-admin
	 * 	- User is logged in
	 * 	- User is an administrator
	 * 	- Query variable is set
	 * 	- Query variable value is correct
	 * @return bool
	 */
	protected function verify_request(){
		return
			is_admin()
			&& current_user_can('manage_options')
			&& isset($_GET[$this->key])
			&& $_GET[$this->key] === $this->pass
			&& isset($_GET['bucket'])
			&& $_GET['bucket']
		? true : false;
	}

	/**
	 * Migration
	 * @return null - exits page load
	 */
	public function migrate_data(){

		#Check again since this is a public method
		if(!$this->verify_request()){
			wp_die("Required params: {$this->key}, bucket");
		}

		set_time_limit(0);

		show_message('<pre>');

		global $wpdb;

		$sql = "
			SELECT p.ID, pm.meta_value path
			FROM $wpdb->posts p
			LEFT JOIN $wpdb->postmeta pm
			ON p.ID = pm.post_id
			WHERE p.post_type = 'attachment'
			AND pm.meta_key = '_wp_attached_file'
		";
		$attachments = $wpdb->get_results($sql);
		$count = count($attachments);
		show_message("<h2>$count Attachments</h2>");
		$errors = 0;
		$error = array();
		foreach($attachments as $attachment){
			$s3_info = array();
			$s3_info['bucket'] = $_GET['bucket'];
			$s3_info['key'] = trim("/content/uploads/$attachment->path", '/');
			if(update_post_meta($attachment->ID, 'amazonS3_info', $s3_info))
				show_message("$count - #$attachment->ID - $attachment->path converted correctly");
			else{
				$errors++;
				$error[] = $attachment->ID.' - '.$attachment->path;
				show_message("<span style='font-weight:bold; color:red;'>$count - #$attachment->ID - $attachment->path was not converted</span>");
			}
			$count--;
		}
		show_message("<h2>$errors Errors</h2><span style='color:red;'");
		print_r($error);
		echo '</span></pre>';
		exit;
	}
}
new Coderrr_S3_Conversion;

Add index.php

Since WordPress is now living in its own separate folder, you will need to copy index.php from the app/ directory, place it in the root and commit it. Edit index.php and add app/ to the path as follows:

/** Loads the WordPress Environment and Template */
require( dirname( __FILE__ ) . '/app/wp-blog-header.php' );

Configure WP Paths

The last thing to do is tell WordPress where everything is at. Place the following into wp-config.php:

/** This should point to the app directory */
define("WP_SITEURL", "http://" . $_SERVER["HTTP_HOST"].'/app');

/** This is the URL your visitors will see */
define('WP_HOME', "http://" . $_SERVER["HTTP_HOST"]);

/** Point both directory and URLs to content/ instead of the default wp-content/ **/
if ( ! defined( 'WP_CONTENT_DIR' ) ) {
	define( 'WP_CONTENT_DIR', __DIR__ . '/content' );
}
if ( ! defined( 'WP_CONTENT_URL' ) ) {
	define( 'WP_CONTENT_URL', WP_HOME . '/content' );
}

Add .htaccess

Since the docroots of Heroku apps are disposable, anything created by Apache will go away with each deployment. Thus, we need to commit a .htaccess file for pretty permalinks.

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

Add HHVM runtime support

To add the HHVM runtime, you will need to set a custom buildpack for your application. As a plugin is to WordPress, so is a buildpack to Heroku builds. This is a simple command using the Toolbelt in your app directory:

heroku config:set BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-php

You can alternatively set this as a config var in the setup tab of your application.

Use Celadon Cedar-14 Stack

This tutorial implements HHVM runtime for faster processing. If your app is using the Celedon Cedar stack, you will need to upgrade to the Celdon Cedar-14 stack which includes the latest versions of the HHVM runtime. HHVM has been deprecated on the previous stacks. This sounds complicated, but it is very simple using the Heroku Toolbelt. Simply type the following command in your app directory:

heroku stack:set cedar-14

From there simply, commit something and push it to heroku master.

Deploy to Heroku

The last step is to deploy your application to Heroku

git push heroku master

Keep Your Dyno Awake

Heroku allows web servers to stay ‘awake’ for one hour without any traffic before tucking them in. The unfortunate user who hits your site while the app is asleep will get quite the load time. The subsequent users will have normal load times. To keep this from happening, I use Kaffeine. It simply pings your heroku app once every ten minutes for free.

Optional: Add Memcachier

If you would like to use Memcache for object caching (transients, etc…), simply install the Memcachier (hosted Memcache) add-on under the Resources tab or use the toolbelt with the following in your app directory:

heroku addons:add memcachier:dev

Once you’ve completed that, add the following to your composer.json file in the ‘require’ section:

"wpackagist-plugin/memcachier": "1.0.1"

Run composer update and then copy content/plugins/memcachier/ to the content directory:

composer update --ignore-platform-reqs;
cp content/plugins/object-cache.php content/.;
//explicitly add since content/ is ignored
git add -f content/object-cache.php;

Now, push your changes to heroku master and it will work automagically.