PHP on Heroku, again

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 "hakre@example.com"

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: hakre@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 git@github.com:hakre/CVBacklog.git
Cloning into 'CVBacklog'... [...]

$ cd CVBacklog/

$ git remote add upstream git@github.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/ | git@heroku.com: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 git@heroku.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?

Procfile:

web:    sh www/conf/web-boot.sh

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

conf/httpd/default.conf:

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.

Read on: Using Composer To Manage Dependencies In Heroku PHP Apps; Scalability: How does Heroku work?

This entry was posted in Hakre's Tips, PHP Development, Pressed, Reports and tagged , , , . Bookmark the permalink.

31 Responses to PHP on Heroku, again

  1. Johnny Carver says:

    The step “scp zlib.so user@host:~/destination” in the compile section is unclear. What should the user/host/destination be? When I try the URL for my Heroku app git directory, it tells me “Permission denied (publickey)”.

    • hakre says:

      scp is used here to copy files between two computers, please see In Unix, how do I use the scp command to securely transfer files between two computers?. So next to heroku you need to have another Unix machine that is accessible from heroku via the internet.

      • louy2 says:

        A bit confused here. Did you mean that I cannot copy it right back to the machine I am using logging onto heroku, but I need another one which is connected to heroku but not used?

        • hakre says:

          No, you just need a machine that is able to scp. If the machine you are using to log into heroku can do scp, you don’t need another one.

          • mlunoe says:

            Ok. First of all, that could have been explained better.
            Second, I do not recommend that you enter the heroku bash to create files as you aren’t actually changing the file for other dynos and it probably will be overwritten if you push something else to the heroku app. Also, it is easier to make changes, if it is part of the app and changes can just be pushed.

            What I did was to download the zlib.so (http://dl.dropbox.com/u/13934041/zlib.so) to my local machine, placed it in ‘libs/php/’ (from the root of my app), created a php.ini file and placed it in the root folder of my app. Used a text editor to write ‘extension=/app/www/libs/php/zlib.so’ on the first line of the php.ini file. This is the reference to the library so that php knows where to find it.
            After this push the changes to my Heroku app.

            This was also explained here: http://blog.pajap.com/?p=102

          • hakre says:

            So how is this different? It uses the bash as well to compile the extension, then downloads it and places it inside the git repo. Like I suggested in my post. If I not even referenced that posting (or a similar one) for that, I remember at least one Symfony2 based posting in my references.

  2. Christian Clough says:

    Hakre, thank you for this tutorial… Do you know if this will allow me to run a php script within a Rails app? I’ve got this php script which is quite large (and hence would take a while to port), that I really want to run within my rails app…

    • hakre says:

      If you install PHP and Rails with your Heroku instance, you can do that. However by default Heroku only supports one programming language environment at a time as it looks like. As said for their default offerings. You then can run your own instance configuration with all the software you need so you are not limited to what Heroku offers out of the box.

  3. Hakre, do you know why Heroku does not officialy support PHP app? For a PHP app would recommend going with Heroku or something like AWS Elastic Beanstalk?

    • hakre says:

      I can not speak for Heroku, but I’d say PHP is officially supported because you do not need to roll your own. I did a bit of rolling my own in this Tutorial but I can imagine you have something similar with other offerings. I find it also hard to give a suggestion between the two you name because it depends what you aim for and those two are different things.

  4. cdev says:

    Hi hakre. Do you have any tips on how to use a scaled process (like a worker) declared in a Procfile?? I understood how to setup the process … but I could not figure out how to bind requests coming from web process to it.

  5. Thanks! This helped me move a tiny PHP app to Heroku in about 20 seconds!

  6. It errors when I try to run phpize:

    Cannot find config.m4.
    Make sure that you run ‘/app/php/bin/phpize’ in the top level source directory of the module

    When I do a pwd, I’m in /app/tmp/php-src/ext/zlib … I also spot the config.m4 its talking about, so its definitely there. Any ideas? Thanks!

    • hakre says:

      I’d say follow the trail of the error message. You’re doing something wrong but I can not see what from the little info you’ve given, sorry.

    • mauriciobomfim says:

      Hi,
      I got it working renaming the file config0.m4 to config.m4

      • Abrar says:

        For a noob, what exactly should i type in to rename config0.m4 to config.m4?

        u46129@31b493f6-be28-4cf8-93db-e67ab848a2fb ~/tmp/php-src/ext/zlib> rename config.m4 config0.m4
        Bareword “config” not allowed while “strict subs” in use at (eval 1) line 1.
        Bareword “m4” not allowed while “strict subs” in use at (eval 1) line 1.

        Got it to work by using mv instead of rename

  7. Thank ou very much for this tutorial.

    I dont understand well why you put “/app/www/src/app/public”” in “conf/httpd/default.conf” file. I have to change it to “/app/www”.

    Thanks!

    • Uploading/updating files or plugins/themes not working. It works the first time but before some minutes, all files get lost. 😦

      • hakre says:

        You tried to install WordPress on Heroku. This tutorial does not cover this application but another sample PHP application. If you want to keep uploads, you have to store it to some file-service like EC2 with Heroku because the file-system is reset when it starts (Ephemeral filesystem). For PHP code updates, like plugins and themes in WordPress, put them under your git sources and just upload them.

    • hakre says:

      Because the sample application I use has its public webroot there. That is just standard Apache HTTPD configuration, you find the directives documented on http://httpd.apache.org/docs/current/ depending on the Apache HTTPD version used.

  8. Motyar says:

    Really not sure how its working for you. When I edit any file using heroku bash its not get changed on server. They says :-
    “Each dyno gets its own ephemeral filesystem, with a fresh copy of the most recently deployed code. During the dyno’s lifetime its running processes can use the filesystem as a temporary scratchpad, but no files that are written are visible to processes in any other dyno and any files written will be discarded the moment the dyno is stopped or restarted.”

    Help me.

    Thanks

    • hakre says:

      Just do not edit files via bash if that edit should be more than a temporary one. It’s just not the right tool for the job. See the other comment here about the Ephemeral filesystem.

  9. Adrien Delorme says:

    Hi,

    I’m not sure if you know but there is now a helper to build php so files : https://github.com/heroku/vulcan

    It would be great if you could extend this arcticle giving some advices on it 🙂

    Thanks for the post !

  10. Pingback: How the heck did I do that ?!» Blog Archive » PHP, Zend Framework 2 and Composer on Heroku

  11. I was having ‘MaxClients’ exceeded error after using facebook php sdk. Your solution really worked! Thanks a lot!

  12. Pingback: PHP on Google App Engine – Quick First Review | hakre on wordpress

  13. Pingback: Deploy PHP websites on Heroku | Codedwell

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.