Adding Custom Binaries To Heroku
April 26, 2013
There are a couple of great articles out there for creating custom buildpacks. The Heroku Documentation has details on creating a Vulcan build server to package custom binaries.
In my case, I needed to add a custom image processing library and update the version of Ghost Script that is distributed via Heroku. I was seeing some weird behaviours from the version of of Ghostscript that is packaged cedar stack on Heroku that were not present in my development environment that was running newer version of Ghostscript. Rather than attempt to troubleshoot odd behaviours from out-dated software release (Heroku currently has version 8.71 installed), wouldn’t it be easier if I could simply update Ghostscript to the latest release (9.07).
In the Heroku world, adding a custom binary isn’t as simple as using bash to connect to the dyno, downloading the software and compiling. To be honest, you could to this, but every time you create a new dyno or push a new update, the binary will be gone. Luckily Heroku allows you to add custom configurations and binaries to your apps through buildpacks.
Setup Vulcan
These general instructions can also be found within the Heroku Documentation
To get started, you must first install Vulcan. I assume that you have Heroku installed an configured in your environment. Vulcan gives you the ability to compile custom binaries for your Heroku dynos.
Use gem to install Vulcan
$ gem install vulcan
Create a new build server on Heroku. Remember, Heroku does not charge for the first dyno you run and you won’t incur additional fees.
$ vulcan create vulcan-my-custom-build-server
Now you can download your software and un-tar the file. In this example, I’m downloading the latest ghost script release.
$ curl -O http://downloads.ghostscript.com/public/ghostscript-9.07.tar.gz
$ tar xzvf ghostscript-9.07.tar.gz
$ cd ghostscript-9.07
Execute the vulcan build command. This will upload the files you have just un-tared to your build server, compile the software, download a package to your local machine and also provide you with a web accessible download link from which you can also download the package.
$ vulcan build
>> Packaging local directory
>> Uploading code for build
>> Building with: ./configure --prefix /app/vendor/ghostscript-9.07 && make install
>> Downloading build artifacts to: /tmp/ghostscript-9.07.tgz
(available at http://vulcan-my-custom-build-server.herokuapp.com/output/56e60ba0-a7cc-11e2-9e96-0800200c9a66)
Upload to S3
You must make the custom binary that you have created available to Heroku to download when a dyno is created. The easiest way to do this is to upload the tar package to a web accessible url. Amazon S3 work well for this. Just remember to make sure the public read permissions are set for the package.
Creating a Custom Buildpack
A custom buildpack is a set of configuration settings used by Heroku whenever a dyno is created. For example, heroku-buildpack-ruby is the standard buildpack Heroku uses when creating a Ruby dyno.
A build pack consists of three files located in a /bin directory and is accessible to Heroku via a git server. To create a build pack, create a new git repo or fork the repo I’ve created for Ghostcript located here.
A buildpack contains three files in a bin directory, namely compile
, detect
and release
.
The compile file provides instructions to download the package and unpack it. I’ve unpackaged the files to the vendor directory.
compile
echo "-----> Installing GhostScript 9.07"
cd $1
curl https://s3.amazonaws.com/clearideas-public/gs.tgz -s -O
mkdir -p vendor/gs
tar -C vendor/gs -xvf gs.tgz
The detect file simply has an echo for the binary that is being installed.
detect
echo "GhostScript 9.07"
exit 0
The release file adds the location of installed binary to Heroku’s PATH
environment variable.
release
cat <<EOF
config_vars:
PATH: $PATH:/app/vendor/gs/bin
EOF
Referencing the Buildpack in Your Application
Now that your binary has been created, is publicly accessible via S3 and you have created instructions telling Heroku what to do with it, you must reference the buildpack in your application. I’ve used heroku-buildpack-multi. This is a custom buildpack that reads a .buildpacks
file in your application’s root directory. The .buildpacks
file allows your Heroku to load multiple buildpacks for your application. In my case, I’ve specified the Ghostscript buildpack I created above, as well as the default Ruby buildpack created by Heroku.
Create a .buildpacks
file in the root directory of your application. Add a url for each buildpack Heroku should use. Use the appropriate Heroku buildpack for your environment.
https://github.com/clearideas/heroku-buildpack-ghostscript.git
https://github.com/heroku/heroku-buildpack-ruby.git
Tell Heroku to use heroku-buildpack-multi as the default buildpack. To do so edit the Heroku configuration setting for your application with the following command.
$ heroku config:add BUILDPACK_URL=https://github.com/ddollar/heroku-buildpack-multi.git
You may need to update references within your application to use the location of the new binary that you have installed. You should now be able to push a new update of your application and your custom binary will be installed on your dyno.