Getting a PHP app running on HerokuSunday, September 16, 2012
We were looking for an easy to set up hosting solution for our new PHP application. A colleague mentioned that we should try Heroku, since they are praised everywhere. I can agree, I don't even know what it is, but I have already seen it praised on various blogs.
I started reading up on what exactly Heroku is, taken from their homepage:
Heroku (pronounced her-OH-koo) is a cloud application platform – a new way of building and deploying web apps. Our service lets app developers spend 100% of their time on their application code, not managing servers, deployment, ongoing operations, or scaling.
Huh. Alright, let's look around more. Oh, there is a how it works page, with cool pictures, let's click through that. I still don't get it. The whole how-it-works page seems very well made, and call me slow, but I still don't get what exactly is this thing. Since I already went through with deploying an app to it, I'm going to sum it up, in my own words:
Heroku is like a server, that is managed by a third party. The whole architecture (PHP version, web server, etc.) is managed by them. You get a git repository address, that you can push code into, and each time you push, it will deploy your code automatically. You can also install a CLI client on your own machine, that you can use to interact with your remote application.
That would have been something that could have made it click it for me right away, but maybe I'm just slow. Heroku doesn't really advertise PHP as a supported language, I'm not sure why, but a little googling tells me that the whole reason PHP support was born, is because of some kind of agreement between them, and Facebook, but take this with a grain of salt.
The problems I encountered
When I first tried to push code onto the remote heroku repo, I was greeted with:
-----> Heroku receiving push ! Heroku push rejected, no Cedar-supported app detected
This was easily googled, and the reason for this is that my application had no index.php in the root folder. In order for Heroku to identify your application as a PHP app, the there must be a index.php, in the root folder. This is a major bad practice, the document root should only contain the public facing files, otherwise you will have to resort to blacklisting every directory that you don't want to be world-readable, and blacklisting is usually a bad choice when it comes to security. Our app was made in Symfony, and the index.php was in a folder called "web". Actually, it wasn't even named index.php (which is something I don't get in Symfony).
Using a dummy index file does the job, so I placed an empty index.php in the root. Next step, is to get Heroku to somehow recognize our web/ folder as the document root. I found a way to hook into the deployment process of Heroku, using a file called Procfile. I got this tip from a Github repo, that I no longer seem to find, but basically you can specify a shell script in your Procfile, which instructs Heroku how to boot your application. I created a file named Procfile in the root, that had a single line:
web: sh www/config/web-boot.sh
This is important, because I think this is the only place (the shell script) where you can actually make permanent changes, that will get propagated to all the nodes (heroku distributes your code to many nodes). In case you are wondering, /www/ is the folder where the files you push to the git repository get deployed. The procfile is ran relative to /app.
So, let's take a look at web-boot.sh:
echo "Include /app/www/config/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
It will place a single line in Apache's conf, which instructs it to include every *.conf file in our applications 'config/httpd' folder. So, I created a virtual host, in our applications config/httpd/default.conf, and pushed it to the git repository:
DocumentRoot "/app/www/web" <Directory "/app/www/web"> Options Indexes FollowSymLinks AllowOverride All Order allow,deny Allow from all </Directory>
This solves the DocumentRoot issue. I'm not sure what exactly the tail -f lines do, but I guess you need to have open file descriptors on them, in order to make the "heroku logs" command display them.
Symfony includes a very useful console interface, that you can use to clear caches, generate your DB from your schema files, and so on. I needed to do the latter. I tried running heroku /app/bin/php /app/www/symfony propel:build-all, which is supposed to generate the database, but it failed with an error:
/app/bin/php: error while loading shared libraries: libmcrypt.so.4: cannot open shared object file: No such file or directory
This references a libmcrypt.so file, which is a PHP module. So, I tried finding where the php.ini file is located on Heroku's server, because I can probably mess around with that using sed/grep/awk in the Procfile, in the same way as I did with httpd.conf. Using "heroku run bash" gives you a pretty much fully functioning shell, and running a "find -name 'php.ini', revealed that it is under /app/php/php.ini.
grep 'libmcrypt' /app/php/php.ini returns:
; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt)
Which is a comment. The libmcrypt.so.4 file that the error message references is not even enabled in php.ini, which leads me to believe that it is the actual linux libmcrypt package that is broken on the Heroku installs, so I did not chase this further. I went ahead and and loaded the database manually, but this a major pita. When the application goes live, doing every database change by hand, then checking the database if they actually went in correctly, is a major source of errors, we really need the symfony console to automate this.
This was the point where I gave up. There are a few other things that I would have needed to figure out, like how to move the cache into memcache or something else (by default Symfony caches into the filesystem, which is a no-no if your app is distributed to many servers), or how to install PHP extensions.
In the end we went with a VPS, but doing some Google searches now, reveals more results, like there is an article that describes how to get a Symfony2 app running, that advocates compiling PHP on Heroku for the various modules you might need, and there is also a PHP Buildpack, which I have been told is a good way to get PHP running, but I'm not entirely sure what it is.
This wasn't such a surprise, the reason they don't really advertise PHP support anywhere is probably because it's still in a pretty early stage. If your application is simpler/smaller, I still think Heroku might be a good choice, and it will probably evolve as time goes on.