How to deploy OCaml on Heroku
Heroku is a PaaS cloud offering (Platform as a Service), where you pay for hosting an application. Compared to a IaaS (Infrastructure as a Service) where you pay for “just” a virtual machine, this means that you get some nice extras, such as easier deployment, system updates, etc. This post describes how we use it to deploy Cryptosense’s backend webservices written in OCaml.
Heroku originally supported only Ruby, but then added support for other languages, such as Python or Node.js. It’s actually flexible and able to run OCaml without any problem.
Let’s try it with a toy service that listens for HTTP requests and counts the number of words in it. That’s what the Cryptosense Analyzer does except it counts problematic usages of cryptography instead of words.
The following OCaml program uses the cohttp library to perform this task:
The interface between this code and Heroku is fairly minimal:
- a file named Procfile defines the command to run.
- the process has access to an environment variable named PORT that defines which port it should listen to (Heroku then handles routing external traffic to your application, including HTTPS).
We can try it locally:
% ocamlfind ocamlopt -package cohttp.lwt -package lwt.ppx -package yojson -linkpkg word_count_server.ml -o word_count_server.native
% ./word_count_server.native &
% curl -d ‘hello world’ http://127.0.0.1:3000/words
% curl -d ‘hello world full of broken cryptography’ http://127.0.0.1:3000/words
Let’s create an Heroku application for this service. If no name is passed, a random one is generated.
% heroku apps:create
Creating app… done, ⬢ salty-sands-52253
https://salty-sands-52253.herokuapp.com/ | https://git.heroku.com/salty-sands-52253.git
By default, Heroku will try to detect what kind of project this is. For example, if there is a requirements.txt file, it is a Python project if a Gemfile is present, it is a Ruby project, etc. It then invokes the correct “buildpack”, a script that installs the dependencies and can take extra steps, like preprocess some code or precompile assets.
It is not possible to set no buildpack at all, so we will use the “null buildpack”, a buildpack that does nothing. It just receives an executable file and runs it. Heroku is just Linux on a 64-bit system, so we can generate a binary and deploy it.
% heroku buildpacks:set http://github.com/ryandotsmith/null-buildpack.git
Buildpack set. Next release on salty-sands-52253 will use http://github.com/ryandotsmith/null-buildpack.git.
Run git push heroku master to create a new release using this buildpack.
Now we just have to send the application to Heroku. There are several ways to do that. The simplest one is to push to a special git remote that will compile and deploy the code. But since we’re sending a binary, it is not a good fit (it would require to check in the binary in the repository). Instead; we will use the heroku-builds plugin:
% heroku plugins:install heroku-builds
Installing plugin heroku-builds…
% heroku builds:create
-----> Null app detected
-----> Nothing to do.
-----> Discovering process types
Procfile declares types -> web
https://salty-sands-52253.herokuapp.com/ deployed to Heroku
Once the deployment has been done, we can see it start in the logs:
% heroku logs
016–08–25T16:15:12.760171+00:00 heroku[slug-compiler]: Slug compilation started
2016–08–25T16:15:12.760176+00:00 heroku[slug-compiler]: Slug compilation finished
2016–08–25T16:15:12.551982+00:00 heroku[api]: Deploy by email@example.com
2016–08–25T16:15:12.552049+00:00 heroku[api]: Release v3 created by firstname.lastname@example.org
2016–08–25T16:15:12.551479+00:00 heroku[api]: Scale to web=1 by email@example.com
2016–08–25T16:15:13.590147+00:00 heroku[web.1]: Starting process with command `./word_count_server.native`
2016–08–25T16:15:16.886189+00:00 heroku[web.1]: State changed from starting to up
And here it is, serving requests in the cloud using OCaml:
% curl -d ‘hello world’ https://salty-sands-52253.herokuapp.com/words
That’s it, we wrote a small server and deployed it to Heroku. There are some limitations on this approach. For example, it is impossible to depend on native libraries. A way to fix that could be to write a OCaml buildpack that understands opam dependencies and builds the binary on Heroku, installing required dependencies.
Interested in webservices and functional programming? Join us and let’s fix the world’s broken cryptography together.