Deploying Yesod Apps to Heroku

Joe Nelson edited this page Feb 27, 2014 · 12 revisions
Clone this wiki locally

Note: this page describes the method of compiling the yesod app locally and uploading it to Heroku's servers. The alternative (and preferred) approach is to use a Haskell Buildpack for Heroku.


Heroku is a cloud platform as a service (PAAS) for building, deploying, and running cloud apps using Ruby and other languages. Their business is in removing the hassle from deploying applications. See Heroku Isn't for Idiots for a non-objective but nonetheless informative blog post.

Heroku doesn't run Haskell or Yesod but it runs binaries. For deploying your Yesod (or any Haskell) app to Heroku you will compile the executable and use git to move it on their servers. Once the setup is done, this becomes an easy process.

3-step guide

  1. Set up a virtual machine to compile your local app. See the step by step guides on this wiki:
  2. Set up Heroku
  3. Deploy to Heroku

This guide assumes you have some working experience with Yesod and Git.

Set up Heroku

Set up your account

  1. Create a free developer account on Heroku
  2. Install the Heroku command line tool. This is included in the virtual machine setup of step 1; otherwise do wget -qO- | sh
  3. Login: heroku login. This will also read and otherwise create a public SSH key on your machine.

If you already have an account, and are working on a different virtual machine, you can pass your public SSH keys to Heroku: heroku keys:add ~/.ssh/ If this doesn't work (Permission denied error), let Heroku create a new key: heroku keys:clear, rm ~/.ssh/, heroku keys:add.

Set up Git

If you've worked with Git this will be familiar. Otherwise read Git for beginners first.

  1. Store your name and email in git config:
    git config --global "Your Name"
    git config --global
  2. Go to your project folder and do:
    git init
    git add .
    git commit -m "init"

The next steps will also create the remote Git repository.

Set up your Heroku app

  1. Go to your project folder, for instance cd /vagrant/appname/.
  2. Create your project on the Heroku server: heroku apps:create appname.
  3. Add the free "Dev Plan" postgresql add-on: heroku addons:add heroku-postgresql. More details at Getting Started with the Heroku Postgres Add-on.
  4. Connect your Heroku app to your database, see the instructions on the "Getting Started" page.
  5. Assign your database to the DATABASE_URL environment variable in your application: Promote your database.

Make changes to your Yesod project

  1. Go to your project folder.
  2. In config/settings.yml change the approot setting in section Production to the URL of your Heroku application, for instance approot: "".
  3. Move deploy/Procfile to the root of the project folder: mv deploy/Procfile ./.
  4. It needs to contain only this one line:
    web: ./dist/build/appname/appname production -p $PORT
  5. Create a package.json file:
    echo '{ "name": "appname", "version": "0.0.1", "dependencies": {} }' >> package.json
  6. Add the changes to git: git add . and git commit -m "heroku setup".

Deploy to Heroku

Instructions to push your app to Heroku without committing the large binary, from your virtual machine.

Repeat these steps to deploy:

git checkout -b deploy
cabal clean && cabal configure -fproduction && cabal build
git add -f dist/build/appname/appname
git commit -m "binary"
git push -f heroku deploy:master
git checkout <original branch>
git branch -D deploy

The critical thing to note is the creation of a new branch before commiting the binary. I do this every time I push to Heroku, after the branch is pushed I delete it so it can be garbage collected by Git (and more importantly, will never make its way out of my local repository). This requires the use of push -f because the branch on Heroku will never be an ancestor of the commit being deployed to heroku.

After pushing the deploy branch checkout the original branch and keep working on your application.

Random tips

Resolve any outstanding library issues.

You'll now have a Yesod app on Heroku but dependent on your app it will not work yet due to missing shared libraries. Even an empty scaffold site requires a library (libgmp) that is not on Heroku by default. If you view the logs for your heroku app you'll likely see something like this:

$ heroku logs
2012-03-08T20:08:55+00:00 heroku[api]: Add-on add logging:basic by
2012-03-08T20:08:55+00:00 heroku[api]: Release v1 created by
2012-03-08T20:11:03+00:00 heroku[slugc]: Slug compilation started
2012-03-08T20:11:26+00:00 heroku[api]: Config add PATH by
2012-03-08T20:11:26+00:00 heroku[api]: Release v2 created by
2012-03-08T20:11:26+00:00 heroku[api]: Release v3 created by
2012-03-08T20:11:26+00:00 heroku[api]: Deploy 5cfe526 by
2012-03-08T20:11:27+00:00 heroku[web.1]: State changed from created to starting
2012-03-08T20:11:27+00:00 heroku[slugc]: Slug compilation finished
2012-03-08T20:11:33+00:00 heroku[web.1]: Starting process with command `./dist/build/appname/appname production -p 11527`
2012-03-08T20:11:34+00:00 app[web.1]: ./dist/build/appname/appname: error while loading shared libraries: cannot open shared object file: No such file or directory
2012-03-08T20:11:35+00:00 heroku[web.1]: Process exited with status 127
2012-03-08T20:11:35+00:00 heroku[web.1]: State changed from starting to crashed
2012-03-08T20:11:35+00:00 heroku[web.1]: State changed from crashed to created
2012-03-08T20:11:35+00:00 heroku[web.1]: State changed from created to starting

Note the line labeld app[web.1] that says "error while loading shared libraries:". This means that when the application was compiled it referenced libgmp which can not be found on the Heroku host. To resolve this create a libs directory under in the project, copy the local shared libraries there, and then push them to Heroku.

First find out if there are any other missing libraries. Run the command heroku run ldd dist/build/appname/appname. ldd will display a list of all the shared libraries a binary depends on along with the path to that library.

$ heroku run ldd dist/build/appname/appname
Running ldd dist/build/appname/appname attached to terminal... up, run.1 =>  (0x00007fff355b8000) => /lib/ (0x00007f5484ead000) => /usr/lib/ (0x00007f5484c85000) => /lib/ (0x00007f5484a6d000) => /lib/ (0x00007f5484850000) => not found => /lib/ (0x00007f54845cc000) => /lib/ (0x00007f54843c4000) => /lib/ (0x00007f54841c0000)
.... SNIP ....

Note the line that ends in => not found. Any libraries that are missing on the Heroku host will show up as lines like this. For your application to work on Heroku you'll need to make a copy of your local libraries available to the Heroku host. Do this by creating a libs directory on the deploy branch and adding your local libraries there.

mkdir libs
$ heroku config:add LD_LIBRARY_PATH=./libs
Adding config vars and restarting app... done, v5

The command heroku config:add ... sets the LD_LIBRARY_PATH environment variable to ./libs. This tells the dynamic linker on the Heroku host to look in the libs directory when searching for shared libraries. Next find where the missing libraries on your local host. To do this use the same command (ldd) locally that you used on Heroku previously.

$ ldd dist/build/appname/appname =>  (0x00007fff7ffff000) => /lib/x86_64-linux-gnu/ (0x00007f4b9cba5000) => /usr/lib/ (0x00007f4b9c979000) => /lib/x86_64-linux-gnu/ (0x00007f4b9c760000) => /lib/x86_64-linux-gnu/ (0x00007f4b9c543000) => /usr/lib/ (0x00007f4b9c2cf000) => /lib/x86_64-linux-gnu/ (0x00007f4b9c04a000) => /lib/x86_64-linux-gnu/ (0x00007f4b9be42000)
.... SNIP ....

Note where the missing libgmp is located (/usr/lib/ on my machine) and copy it into your libs directory.

cp /usr/lib/ ./libs/

Repeat this step for any other missing libraries then commit and push your libraries to the heroku remote. You should now have a working Yesod app on Heroku.

GLIBC version errors

If you use a rolling release Linux distribution like I do your version of libc may be newer than the version on Heroku. In that case you'll get errors that look something like the following.

2011-12-28T01:31:23+00:00 app[web.1]: ./dist/build/appname/
appname: /lib/ version `GLIBC_2.15' not found
(required by ./dist/build/appname/appname)
2011-12-28T01:31:23+00:00 app[web.1]: ./dist/build/appname/
appname: /lib/ version `GLIBC_2.15' not found
(required by /app/dist/build/appname/
2011-12-28T01:31:25+00:00 heroku[web.1]: State changed from starting to crashed
2011-12-28T01:31:25+00:00 heroku[web.1]: Process exited

There are apparently several ways to fix this but the simplest I've found is to build on a distro that has an older version of libc, which is described in step 1 above.

If you want to compare libc versions you can run the following commands.

$ heroku run --app appname 'ldd --version'
Running ldd --version attached to terminal... up, run.1
ldd (Ubuntu EGLIBC 2.11.1-0ubuntu7.8) 2.11.1
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
Written by Roland McGrath and Ulrich Drepper.
$ ldd --version
ldd (GNU libc) 2.15
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
Written by Roland McGrath and Ulrich Drepper.

As I'm writing this Heroku is running Ubuntu with libc version 2.11.1 while my local machine running ArchLinux has libc version 2.15

Other how-to resources