Deploying Clojure Apps to Heroku with Docker

In this post, you’ll learn how to deploy a Docker-based Clojure application to Heroku using the Heroku Docker CLI. We’ll use the Immutant Feature Demo as an example, but you can follow along with any Clojure application as long as it uses Leiningen to build an uberjar. This is a Mac and Linux guide only (until Docker supports docker-compose on Windows).

Prerequisites

You’ll need a few pieces of software before you get started:

You’ll also need to create a free Heroku account. Then login from the terminal like so:

$ heroku login

Once that’s complete, you can install the Heroku Docker CLI with this command:

$ heroku plugins:install heroku-docker

Now you’re ready to deploy.

Deploying an App

To begin, clone the Immutant demo app to your local machine (if you’d prefer a bare-bones Clojure app you can substitute this Ring app):

$ git clone https://github.com/immutant/feature-demo
$ cd feature-demo

The app is already prepared for Heroku. It contains a Procfile, which tells Heroku how to run the app, and an app.json file that contains some meta-data about the app. The important part of the app.json file is the "image" element, shown below:

{
  "name": "Immutant Feature Demo",
  "description": "A template for getting started with the popular Immutant framework.",
  "website": "http://immutant.org",
  "success_url": "/index.html",
  "addons": ["heroku-postgresql"],
  "image": "heroku/clojure"
}

The "image" element is what Heroku uses to determine the base Docker image to run the container from. The "addons" element determines what additional services will be attached to your container. The Heroku currently supports Postgres, Redis and a few others services with more to come. Given this configuration, we can initialize the app with the following command:

$ heroku docker:init
Wrote Dockerfile
Wrote docker-compose.yml

This created a Dockerfile based on the heroku/clojure image and a docker-compose.yml that constructs the environment (including a local database running in a Docker container).

Now run this command to start the application in a container:

$ docker-compose up web
...
Step 0 : RUN lein uberjar
 ---> Using cache
 ---> ada3689e717a
Successfully built ada3689e717a
...

The first time you run this it will take a while as Leiningen downloads the app’s dependencies into the Docker container. But don’t worry, they’ll be cached.

When the container has started, you’ll see some output like this:

web_1              | boop
web_1              | boop
web_1              | beep
web_1              | boop

That’s Immutant demonstrating it’s scheduling feature.

Open the app in a browser by running this command:

$ open http://$(docker-machine ip default):8080

After you’ve played around with some of the features, like WebSockets, you can deploy the app to Heroku. First, provision a new app thusly:

$ heroku create
Creating limitless-mesa-1279... done, stack is cedar-14
https://limitless-mesa-1279.herokuapp.com/ | https://git.heroku.com/limitless-mesa-1279.git

And deploy to Heroku with the Docker CLI

$ heroku docker:release
Remote addons: heroku-postgresql (1)
Local addons: heroku-postgresql (1)
Missing addons:  (0)
Creating local slug...
Building web...
...
uploading slug...
releasing slug...
Successfully released limitless-mesa-1279!

Then you can open the app with this command:

$ heroku open

Note that when using WebSockets in Firefox, you’ll need to use an http:// addres instead of the https:// that Heroku defaults to.

Development Workflow

In your normal workflow, you’d want to make some changes and see them appear in the Docker container. We’ll demonstrate how that works. Open the src/demo/scheduling.clj file and look for this code:

;; start a couple of jobs, along with a job to stop them in 20 seconds
(let [beep (sch/schedule #(println "beep") every-5s)
      ;; schedule a clj-time sequence
      boop (immutant.scheduling.joda/schedule-seq #(println "boop") (every-3s-lazy-seq))]
  (sch/schedule
    (fn []
      (println "unscheduling beep & boop")
      (sch/stop beep)
      (sch/stop boop))
    (in 20 :seconds)))

Change the "beep" string on the first line to "crocodile". Save the file, and then run these commands to rebuild the image:

$ docker-compose build web
Building web...
Step 0 : FROM heroku/clojure
...
Removing intermediate container aedc7b201c86
Successfully built 6bd2d6e9a3ba

$ docker-compose up web
...
web_1              | boop
web_1              | boop
web_1              | crocodile
web_1              | boop

Open the app in a browser again and navigate to the /hello path. You’ll see your changes. Each time modify your app, you need to re-build the image and then launch the up command. You can also get terminal access to the image by running the shell command thusly:

$ docker-compose run shell
Building shell...
...
root@7c7b5905b2a0:~/user#

From this shell, you can run one-off tasks like database migrations.

Heroku’s Docker support is currently in beta. As we work to make the integration better, we’d love to hear your feedback so we can focus on building the things you need. Feel free to reach out to me directly with you thoughts and ideas.

You can visit the Heroku Dev Center for more information on Heroku’s Docker CLI. And you can learn more about Immutant and Docker from their respective documentation sites. You can also find more information about deploying Clojure apps to Heroku on the Dev Center.