Deploying a Symfony2 app using Doctrine and PostgreSQL to Heroku
Sun, Nov 2, 2014What is Heroku
Heroku is a platform to quickly and easily deploy your applications to. They abstract much of the sysadmin-type work away so the developer only has to stay at the application-level. If you get everything configured correctly you can end up with a single button click deployment.
The free-tier is more than enough to get started and be able to get a sample Symfony2 application up and running with a database backend.
Getting Symfony2 on Heroku
First follow this documentation found on the Heroku site to get a Symfony2 application up and running:
https://devcenter.heroku.com/articles/getting-started-with-symfony2
Making a deploy button
Heroku has documentation on how to do this
https://devcenter.heroku.com/articles/heroku-button
Essentially you create an app.json
file that describes the application and tells Heroku how to deploy it.
Using Doctrine with Symfony2 on Heroku
The above documentation gets you to the point where you have a fully functioning Symfony2 application running on Heroku, however it stops there. If you want your application to be database driven and running on the free-tier of Heroku there are a couple more steps you have to take.
Since you install the standard edition in the tutorial, Doctrine is already included as a dependency in your project.
composer.json
...
"require": {
"php": ">=5.3.3",
"symfony/symfony": "2.6.x-dev",
"doctrine/orm": "~2.2,>=2.2.3",
"doctrine/doctrine-bundle": "~1.2",
"twig/extensions": "~1.0",
"symfony/assetic-bundle": "~2.3",
"symfony/swiftmailer-bundle": "~2.3",
"symfony/monolog-bundle": "~2.4",
"sensio/distribution-bundle": "~3.0",
"sensio/framework-extra-bundle": "~3.0",
"incenteev/composer-parameter-handler": "~2.0"
},
...
Heroku provides a free instance of a PostgreSQL database so we are going to use that for this example. There also appears to be a MySQL equivalent (ClearDB). Most steps should be able to apply to that too but with replacing PostgreSQL with MySQL.
(Optional) Include PDO_PGSQL as a composer extension dependency
This tells Heroku to enable/install the pdo_pgsql.so
extension for your application. This is optional however since pdo_pgsql.so
is one of the automatically installed extensions.
composer.json
"require": {
...
"ext-pdo": "*",
"ext-pdo_pgsql": "*",
...
},
Add the PostgreSQL Heroku add-on
After you have deployed your application, use the heroku
command to enable the PostgreSQL add-on for your application:
$ heroku addons:add heroku-postgresql:hobby-dev
Add it to your app.json
file also to have it automatically enabled for your deploy button
app.json
{
...
"addons": [
"heroku-postgresql:hobby-dev"
]
}
Automatically set your database parameters
When you add the PostgreSQL addon, Heroku automatically provisions a PostgreSQL database for you and provides the access credentials via an environment variable: DATABASE_URL
. Unfortunately thats all they provide you, so you have to parse the URL to put it in a format acceptable for Doctrine.
Using a combination of Composer post/pre install commands and some custom code to pull and push variables to the environment, we can get Doctrine properly configured to run correctly when deployed.
Parse and populate the environment variables
I created a simple static callback to grab the DATABASE_URL
variable, parse it, and stick other variables back in.
HerokuDatabase.php
<?php
namespace Acme\DemoBundle;
use Composer\Script\Event;
class HerokuDatabase
{
public static function populateEnvironment(Event $event)
{
$url = getenv("DATABASE_URL");
if ($url) {
$url = parse_url($url);
putenv("DATABASE_HOST={$url['host']}");
putenv("DATABASE_USER={$url['user']}");
putenv("DATABASE_PASSWORD={$url['pass']}");
$db = substr($url['path'],1);
putenv("DATABASE_NAME={$db}");
}
$io = $event->getIO();
$io->write("DATABASE_URL=".getenv("DATABASE_URL"));
}
}
Update composer.json to run HerokuDatabase::populateEnvironment
Add this as a preinstall step to get the environment populated earlier than the configuration stage
composer.json
"scripts": {
"pre-install-cmd": [
"Acme\\DemoBundle\\HerokuDatabase::populateEnvironment"
],
"post-install-cmd": [
"Incenteev\\ParameterHandler\\ScriptHandler::buildParameters",
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache",
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets",
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile"
],
"post-update-cmd": [
"Incenteev\\ParameterHandler\\ScriptHandler::buildParameters",
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache",
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets",
"Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile"
]
},
Note: Ensure that you include the buildParameters callback in your post install/update commands
Configure composer-parameter-handler to use the new variables for the database configuration
The heroku provided buildpack for PHP runs composer in the non-interactive mode. This means that the behavior of composer-parameter-handler is to just copy and paste the parameters.yml.dist
file to parameters.yml
.
Luckily composer-parameter-handler is configurable to automatically replace some parameters with environment variables.
In our case, it would look like:
composer.json
"extra": {
"incenteev-parameters": {
"file": "app/config/parameters.yml",
"env-map": {
"database_host": "DATABASE_HOST",
"database_port": "DATABASE_PORT",
"database_name": "DATABASE_NAME",
"database_user": "DATABASE_USER",
"database_password": "DATABASE_PASSWORD"
}
}
}
Initialize the database
Almost done, now we just need to initialize the database. Using the heroku command, run the schema creation command:
$ heroku run php app/console doctrine:schema:create
For automation of the deployment button:
app.json
"scripts": {
"postdeploy": "php app/console doctrine:schema:create"
}
Fin
And you’re done! On the first run of your application it may take a while to load (cache is warming up), however every subsequent page load should be fast :)