Setting up a PHP project for the Heroku platform is straight-forward if you know how. The information is a bit scattered, that’s why I write my own little post about it so I don’t have to remember this my own (as it’s a second setup I do and actually I don’t think you should have problems too long with Heroku just to give it some test-drive).
This tutorial will show how to setup a PHP application on Heroku and to deal with common deficiencies like installation of missing PHP extensions and Apache webserver configuration.
Sign-Up and Setup
If you have not used Heroku so far, first step is to sign-up with them. You need a valid email address for your account, because they’ll send you a confirmation email which contains a link you need to request to open your account. You can try Heroku for free, it’s then in some limited mode but you can start to play around with it.
After sign-up and verification, you select your password. Your username is your email-address. This is done all on their website.
Heroku generally works with a command-line interface. To use that interface, you need to install the Heroku Toolbelt package for your operating system.
If you have not so far, you can generate your ssh key in you $HOME/.ssh directory:
> ssh-keygen -f id-heroku-hakre_rsa -t rsa -C "firstname.lastname@example.org"
I suggest you do it before logging into Heroku because you than have better control because next step is to login and it will ask you for the ssh key so it’s better you have one at hand if you don’t want to re-use an existing one (it’s highly suggested you create a new ssh key so you can share it later on project wise). The Heroku command-line application does not allow you to create a new key if you don’t want to reuse one.
You then log into Heroku and select the key (I use git bash from git for windows here):
$ heroku login Enter your Heroku credentials. Email: email@example.com Password (typing will be hidden): Found the following SSH public keys: 1) id-heroku-hakre_rsa.pub 2) id_rsa.pub Which would you like to use with your Heroku account? 1 Uploading SSH public key C:/Users/hakre/.ssh/id-heroku-hakre_rsa.pub Authentication successful.
Create a PHP application on Heroku
The documentation about how to create a Heroku based PHP application is not given literally. Heroku only advertises a “Facebook” application, but it has a lot of overhead to go through the docs because you normally don’t want to create a Facebook application but just a little PHP application.
What you actually only need to know is how to add a new Heroku application with PHP support. And it actually is pretty straight forward. However I make things a bit more exemplary here.
Instead of a Facebook application we do some other standard PHP web-application. I’ve chosen one that is available on github so you can clone it right away: gooh / CVBacklog, a small Stackoverflow related helper application. We’ll port it to Heroku. So after you’ve verified you could login into Heroku, let’s first create the application locally. Fork the repository on github into your own account and then clone it, create the Heroku application and publish the application:
$ git clone firstname.lastname@example.org:hakre/CVBacklog.git Cloning into 'CVBacklog'... [...] $ cd CVBacklog/ $ git remote add upstream email@example.com:gooh/CVBacklog.git $ git checkout -b heroku Switched to a new branch 'heroku'
That has cloned the CVBacklog PHP application, set the appropriate upstream and now the code is available for our own processing in it’s own branch ‘heroku’. Let’s create the Heroku application using the latest Cedar Stack:
$ heroku create --stack cedar cvbacklog Creating cvbacklog... done, stack is cedar http://cvbacklog.herokuapp.com/ | firstname.lastname@example.org:cvbacklog.git Git remote heroku added
The remote heroku has been added, and we now use it to deploy the application via git. It always need to be pushed into the master branch (even we use locally the heroku branch), otherwise heroku will ignore it. Additionally, we need to tell Heroku Cedar that this is PHP by giving a 0-byte index.php file:
$ touch index.php $ git add index.php $ git commit -m "PHP Cedar support." [heroku 9167e9f] PHP Cedar support. 0 files changed create mode 100644 index.php $ git push heroku heroku:master Counting objects: 109, done. Delta compression using up to 2 threads. Compressing objects: 100% (80/80), done. Writing objects: 100% (109/109), 14.82 KiB, done. Total 109 (delta 19), reused 106 (delta 19) -----> Heroku receiving push -----> PHP app detected -----> Bundling Apache version 2.2.22 -----> Bundling PHP version 5.3.10 -----> Discovering process types Procfile declares types -> (none) Default types for PHP -> web -----> Compiled slug size is 9.5MB -----> Launching... done, v3 http://cvbacklog.herokuapp.com deployed to Heroku To email@example.com:cvbacklog.git * [new branch] heroku -> master
A basic “application” is now already online at http://cvbacklog.herokuapp.com/, but it’s just a blank page (before the first push, it was a placeholder). Even PHP get’s executed (hence the blank page), this is not really cool yet. Fixing time.
Fixing the Outcome
Compile PHP zlib extension for Heroku
There is a major drawback with the Heroku platform if you’re looking for PHP because it is missing common extensions, for example zlib functions. However that one is needed by the CVBacklog application so we need to install it. This can be done by opening a bash to Heroku, download the PHP source-code and compile the extension, then copying it over:
heroku run bash export PS1="\u@\h \w> " mkdir tmp cd tmp git clone https://github.com/php/php-src.git -b PHP-5.3 cd php-src cd ext/zlib /app/php/bin/phpize ./configure –with-php-config=/app/php/bin/php-config make cd modules scp zlib.so user@host:~/destination exit
Installing PHP zlib extension for Heroku
After the file has been copied over, I placed it:
$ mkdir -p lib/php $ mv zlib.so lib/php $ echo "extension = /app/www/lib/php/zlib.so" > php.ini
I move the PHP file into a directory called lib/php and create the php.ini file pointing to that extension. The changes are committed.
Creating the cache directory
Next to the requirements of zlib, there’s a hidden hard-encoded caching directory that should be created to give the application some performance:
$ mkdir src/app/cache $ touch src/app/cache/.placeholder
This is going to be committed and pushed:
$ git add php.ini lib/ src/app/cache/ $ git commit -m "Zlib extension and cache directory." $ git push heroku heroku:master
This is also demonstrated by Symfony2 on heroku (other php frameworks too)
Finally it’s time for the testdrive! If you take a look into the application’s git-tree, you see that the CVBacklog’s application webroot is src/app/public, going to http://cvbacklog.herokuapp.com/src/app/public/ displays it. The first time you request it, it will take a little longer because it fetches data from a remote API.
The URL layout is not ideal, however, it runs and display a list of close and delete votes – the CVBacklog.
Before configuring the webroot you saw that the application was not working. What needed to be done was to extend the PHP installation on the Heroku web node to have support for the zlib extension and to create a directory. This has been done now and we can go on to configure the Apache HTTP server on heroku.
Configure the Webroot
Now comes the next tricky part that is specifying the webroot. Specifying the webroot needs a little bit more work and background information. The CVBacklogs applications webroot in the git-tree is src/app/public. For Heroku, by default, the webroot is the root of the git-tree. That directory is internally mapped to /app/www btw. So what this needs is to create a so called Procfile that starts a sh-script each time the Heroku app web-node starts. That script then modifies the Apache configuration and includes your own config which is setting the webroot to /app/www/src/app/public. So we create the procfile, a config directory, the script and the Apache configuration. Ready?
web: sh www/conf/web-boot.sh
sed -i 's/Listen 80/Listen '$PORT'/' /app/apache/conf/httpd.conf sed -i 's/^DocumentRoot/# DocumentRoot/' /app/apache/conf/httpd.conf sed -i 's/^ServerLimit 1/ServerLimit 8/' /app/apache/conf/httpd.conf sed -i 's/^MaxClients 1/MaxClients 8/' /app/apache/conf/httpd.conf for var in `env|cut -f1 -d=`; do echo "PassEnv $var" >> /app/apache/conf/httpd.conf; done echo "Include /app/www/conf/httpd/*.conf" >> /app/apache/conf/httpd.conf touch /app/apache/logs/error_log touch /app/apache/logs/access_log tail -F /app/apache/logs/error_log & tail -F /app/apache/logs/access_log & export LD_LIBRARY_PATH=/app/php/ext export PHP_INI_SCAN_DIR=/app/www echo "Launching apache" exec /app/apache/bin/httpd -DNO_DETACH
DocumentRoot "/app/www/src/app/public" <Directory "/app/www/src/app/public"> Options Indexes FollowSymLinks AllowOverride All Order allow,deny Allow from all </Directory>
After those files are created, add them, commit and deploy via push:
$ git add Procfile conf/ $ git commit -m "Apache webroot configuration." $ git push heroku heroku:master
This is also demonstrated by winglian in Heroku-PHP.
So now everything is in order, visiting http://cvbacklog.herokuapp.com/ will bring us straight into the real webroot. As usual, the first request takes a bit longer because Heroku resets the file-system so the cache is cleared with each deploy. The git repository is mirrored on my github account: https://github.com/hakre/CVBacklog/tree/heroku.
And that’s the end of this tutorial. I hope it’s useful to get the first steps into Heroku and PHP, there’s more to tweak obviously and next steps might be post-deploy scripts to warm-up the cache and/or looking into the heroku-buildpack-php.