This experiment is triggered by many things:
- I like Haskell
- I like docker too
- And I like how easy is Heroku
- Haskell Web Server in a 5MB Docker image
- Introducing 'heroku docker:release': Build & Deploy Heroku Apps with Docker
evening-earth-1057.herokuapp.com - Working example.
Deploying Haskell to heroku isn't simple, there are different buildpacks:
With both buildpacks, and Heroku overall, I don't like that building happens in the cloud. A bit like in the AWS ELB blog post, I'd rather build app locally (or on CI machine, or in build cloud), and deploy artifacts to Heroku.
There are two approaches!
Using inline-build-pack, we can build our app locally, bundle artifacts, and push to Heroku.
All three scripts: detect
, release
, and compile
turn out to be no-op.
Looks like that on cedar14
stack, there is libgmp.so.10
, so we don't need to bundle it anymore, as heroku-buildpack-ghc
does.
Turns out that null-buildpack could be enough too.
Still one problem still exist: how to build a heroku-runnable binary executable on e.g. OSX? Here the docker comes to help!
Using two simple scripts: prebuild
and compilewithdocker
we produce needed executable.
But committing artifacts to the repository is highly unelegant!
We can build an own Dockerfile
to build the Haskell binaries. It's actually quite easy.
Unfortunately (maybe for good) Dockerfiles doesn't support multi-inheritance, so we need to install GHC manually.
Fortunately I have previously done docker ghc image, so we can just copy paste GHC installing spells.
After that we install few dependencies† so we don't need to rebuild everything when deploying.
And finally we build our application and leave it in /app
directory which is then picked up by heroku deployment.
The first build takes time as then the build image is built, it took about 15 minutes on my machine.
The heroku docker:release
takes under one minute. After update to use
stackage-update
in place of cabal update
the heroku docker:release
takes about half a minute.
Using docker deployment you can tweak the build process, which is a huge win. Heroku infrastructure makes the other parts of deployment process and devops easy too. The only negative aspect, with greater power comes greater responsibility, you can accidentally deploy uncommited changes.
- † we could add cabal file to docker and install exact dependencies
- there is
.dockerignore
to ignore host cabal sandbox one uses for development - This approach could be used to deploy scala (which takes enormous time to build e.g. play2 app), or to make Clojure deploy using uberjar.