When I was asked to deploy our newest ExpressionEngine (EE) project to Heroku, it wasn’t a question of if it would work, but how. We suddenly were very serious about ceasing the practice of managing the servers on which our applications run. EE2 represents the last bastion of server hosting at Sparkbox. In many ways, it was designed around a traditional LAMP stack, being somewhat dependent on specific software packages and a persistent disk. A more modern approach, however, is to point the app at web services and for configuration options to be discovered through well-documented conventions. Getting it to work on a hosted platform means finding creative ways to mimic the assumptions built into the system. I’ll explain where there is dissonance between EE and Heroku (our chosen hosted platform) and how we’ve worked around it.
Less Grunt Work
First, it might be interesting to talk more about why Heroku is so important to us. Sparkbox exists to inspire and empower a web built right. We are a team of designers, developers, and managers who pour out creative juices into our clients’ presence on the web. Platforms like Heroku relieve us of server maintenance tasks like updating SSL certificates and responding to server vulnerabilities. It also speeds development. Our static web projects, such as pattern libraries, can be deployed to Heroku within seconds of creating a Git repo and without any need to write deployment scripts. Several months later, when that bug fix or maintenance request comes from the customer, someone will ask, “Was that app on QA server 1 or 2?” Neither, it’s on Heroku, with a searchable name.
Heroku + EE
So, what makes EE so challenging to deploy to a hosted platform like Heroku? For starters, EE is configured through several files on disk. Because configuration values change for each environment, and often contain sensitive information, they aren’t usually deployed with the app. They’re stored elsewhere on the server, and the deploy processes tell the app where to find them. Heroku deployments have no permanent disk to keep these files around, so configuration files must be deployed with the app. To work around this, I modified the necessary portions of EE’s configuration files to look for environment variables. Where before EE would look for hard-coded values, now it would ask the server which value to use. Heroku provides many ways to set these variables. They can be set from the web control panel, the developer’s command line interface, or set dynamically by your application on startup.
That last option helped us clear one last hurdle: configuring the database. EE relies on having the location, name, and credentials to a database in separate values. Heroku, or rather the available MySQL addons, present database configuration as a URL. Ideally, we’d prefer some flexibility to have these individual values set by Heroku, but a more pragmatic solution for us has been parsing that URL ourselves into its individual elements. It works and allows us to move on without relying on the service provider. Further reflecting on this process, I think we’ll try and move this step outside our app next time, possibly as an extension to Heroku’s PHP buildpack.
At some point when building a CMS, you’re going to want to attach images and documents to your content. I bring it up because of something I mentioned earlier: Heroku doesn’t give your app a persistent disk, and EE doesn’t have any other options for file uploads by default. What we do have available to us is the Assets plugin by Pixel and Tonic. Though not entirely seamless, it does allow us to choose between a local directory or cloud storage provider. The plugin differentiates between the two options by requiring a suffix on your input
tag’s name
attribute; either _source
for a cloud provider or _filedir
for a local directory. We account for this when we compile our templates, and since templates will be compiled just-in-time on Heroku, that gives us an opportunity to inject that configuration option using one of the many options I outlined above.
Heroku + EE + Review Apps
Heroku has been testing a new feature recently called Review Apps, which are separate instances of your app it creates when you open a pull request on Github. Review apps have been extremely valuable for us, but now that we have the added complexity of a database, we had to figure out how to get these ephemeral apps to all share it. As mentioned above, database configuration is provided to an app in the form of a URL stored in an environment variable. Review Apps can inherit environment variables if they aren’t explicitly provided. It requires defining them in Heroku’s app.json
and declaring them as required. We have done this before, but never with settings we did not completely own. Turns out, it works just fine with variables provided by the platform, and all our review apps share the database attached to the main application.
{
"env": {
"DATABASE_URL": {
"description": "Location of, and credentials to, the database instance for this app.",
"required": true
}
}
}
This EE app will not run on Heroku in production, so this app exists only as a testing environment. Had we needed to keep production and testing data separate, we would have utilized Heroku’s Pipelines and explicitly defined database providers for production and staging apps. Review Apps then inherit the database configuration from staging. There’s no doubt that there is more to flesh out in making this work, but it’s out of scope for us for now.
A Sigh of Relief
I began this project skeptical of my own success but have found that the PHP community and Heroku have both done some great work to make this setup possible and even productive. Getting this far with EE on Heroku was a real challenge for us, but it’s one that will pay dividends many times the cost over the long term, both in increased testing capabilities and in reduced devops overhead. I’m excited to see what benefits lay ahead for us as we earnestly move away from managing our own servers and are able to focus our efforts in areas that can better benefit both us and our clients.