Skip Navigation
Show nav
Heroku Dev Center
  • Get Started
  • Documentation
  • Changelog
  • Search
  • Get Started
    • Node.js
    • Ruby on Rails
    • Ruby
    • Python
    • Java
    • PHP
    • Go
    • Scala
    • Clojure
  • Documentation
  • Changelog
  • More
    Additional Resources
    • Home
    • Elements
    • Products
    • Pricing
    • Careers
    • Help
    • Status
    • Events
    • Podcasts
    • Compliance Center
    Heroku Blog

    Heroku Blog

    Find out what's new with Heroku on our blog.

    Visit Blog
  • Log inorSign up

Getting Started on Heroku with Clojure

Introduction

This tutorial will have you deploying a Clojure app in minutes.

Hang on for a few more minutes to learn how it all works, so you can make the most out of Heroku.

The tutorial assumes that you

  • A verified Heroku Account
  • Clojure and Leiningen installed locally
  • An Eco dynos plan subscription (recommended)

As of November 28th, 2022, free Heroku dynos, free Heroku Postgres and free Heroku Data for Redis plans are no longer available.

We recommend using our low-cost plans to complete this tutorial. Eligible students can apply for platform credits through our new Heroku for GitHub Students program.

Set up

The Heroku CLI requires Git, the popular version control system. If you don’t already have Git installed, complete the following before proceeding:

  • Git installation
  • First-time Git setup

In this step you’ll install the Heroku Command Line Interface (CLI). You use the CLI to manage and scale your applications, provision add-ons, view your application logs, and run your application locally.

Download and run the installer for your platform:

apple logomacOS

$ brew tap heroku/brew && brew install heroku

windows logoWindows

Download the appropriate installer for your Windows installation:

64-bit installer

32-bit installer

Once installed, you can use the heroku command from your command shell.

On Windows, start the Command Prompt (cmd.exe) or Powershell to access the command shell.

Use the heroku login command to log in to the Heroku CLI:

$ heroku login
heroku: Press any key to open up the browser to login or q to exit
 ›   Warning: If browser does not open, visit
 ›   https://cli-auth.heroku.com/auth/browser/***
heroku: Waiting for login...
Logging in... done
Logged in as me@example.com

This command opens your web browser to the Heroku login page. If your browser is already logged in to Heroku, simply click the Log in button displayed on the page.

This authentication is required for both the heroku and git commands to work correctly.

If you’re behind a firewall that requires use of a proxy to connect with external HTTP/HTTPS services, you can set the HTTP_PROXY or HTTPS_PROXY environment variables in your local development environment before running the heroku command.

Prepare the app

In this step, you will prepare a sample application that’s ready to be deployed to Heroku.

If you are new to Heroku, it is recommended that you complete this tutorial using the Heroku-provided sample application.

However, if you have your own existing application that you want to deploy instead, see this article to learn how to prepare it for Heroku deployment.

To clone the sample application so that you have a local version of the code that you can then deploy to Heroku, execute the following commands in your local command shell or terminal:

$ git clone https://github.com/heroku/clojure-getting-started.git
$ cd clojure-getting-started

You now have a functioning git repository that contains a simple application as well as a project.clj file, which is used by Clojure’s dependency manager, Leiningen.

Deploy the app

In this step you will deploy the app to Heroku.

Using dynos to complete this tutorial counts towards your usage. Delete your app as soon as you are done to control costs.

 

By default, apps use Eco dynos if you are subscribed to Eco. Otherwise, it defaults to Basic dynos. The Eco dynos plan is shared across all Eco dynos in your account and is recommended if you plan on deploying many small apps to Heroku. Learn more here. Eligible students can apply for platform credits through our Heroku for GitHub Students program.

Create an app on Heroku, which prepares Heroku to receive your source code:

$ heroku create
Creating sharp-rain-871 in organization heroku... done, stack is heroku-20
http://sharp-rain-871.herokuapp.com/ | https://git.heroku.com/sharp-rain-871.git
Git remote heroku added

When you create an app, a git remote (called heroku) is also created and associated with your local git repository.

Heroku generates a random name (in this case sharp-rain-871) for your app, or you can pass a parameter to specify your own app name.

Now deploy your code:

$ git push heroku main
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Clojure (Leiningen 2) app detected
remote: -----> Installing JDK 1.8... done
...
remote: -----> Installing Clojure 1.10.0.411 CLI tools
remote:        Downloading and expanding tar
...
remote: -----> Installing Leiningen
remote:        Downloading: leiningen-2.9.1-standalone.jar
remote:        Writing: lein script
remote: -----> Building with Leiningen
remote:        Running: lein uberjar
...
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote:
remote: -----> Compressing...
remote:        Done: 74.7M
remote: -----> Launching...
remote:        Released v3
remote:        http://sharp-rain-871.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/sharp-rain-871.git
 * [new branch]      main -> main

The application is now deployed. Ensure that at least one instance of the app is running:

$ heroku ps:scale web=1

Now visit the app at the URL generated by its app name. As a handy shortcut, you can open the website as follows:

$ heroku open

View logs

Heroku treats logs as streams of time-ordered events aggregated from the output streams of all your app and Heroku components, providing a single channel for all of the events.

View information about your running app using one of the logging commands, heroku logs --tail:

$ heroku logs --tail
2019-08-13T14:48:12.515905+00:00 heroku[web.1]: State changed from starting to up
2019-08-13T14:48:12.296710+00:00 app[web.1]: 2019-08-13 14:48:12.296:INFO:oejs.ServerConnector:main: Started ServerConnector@669c2b07{HTTP/1.1}{0.0.0.0:33805}
2019-08-13T14:48:12.305018+00:00 app[web.1]: 2019-08-13 14:48:12.296:INFO:oejs.Server:main: Started @9949ms
2019-08-13T14:48:13.874066+00:00 heroku[router]: at=info method=GET path="/" host=murmuring-atoll-71587.herokuapp.com request_id=3ee01cce-c0b7-41f0-8e5c-f7df537dd493 fwd="62.17.146.153" dyno=web.1 connect=2ms service=428ms status=200 bytes=169 protocol=https

Visit your application in the browser again, and you’ll see another log message generated.

Press Control+C to stop streaming the logs.

Define a Procfile

Use a Procfile, a text file in the root directory of your application, to explicitly declare what command should be executed to start your app.

The Procfile in the example app you deployed looks like this:

web: java $JVM_OPTS -cp target/clojure-getting-started-standalone.jar clojure.main -m clojure-getting-started.web

This declares a single process type, web, and the command needed to run it. The name web is important here. It declares that this process type will be attached to the HTTP routing stack of Heroku, and receive web traffic when deployed.

Procfiles can contain additional process types. For example, you might declare one for a background worker process that processes items off of a queue.

Scale the app

Right now, your app is running on a single web dyno. Think of a dyno as a lightweight container that runs the command specified in the Procfile.

You can check how many dynos are running using the ps command:

$ heroku ps
=== web (Eco): `java $JVM_OPTS -cp target/clojure-getting-started-standalone.jar clojure.main -m clojure-getting-started.web`
web.1: up 2014/04/25 16:26:38 (~ 1s ago)

By default, your app is deployed on an eco dyno. Eco dynos will sleep after a half hour of inactivity (if they don’t receive any traffic). This causes a delay of a few seconds for the first request upon waking. Subsequent requests will perform normally. Eco dynos also consume from a monthly, account-level quota of eco dyno hours - as long as the quota is not exhausted, all eco apps can continue to run.

To avoid dyno sleeping, you can upgrade to a Basic or Professional dyno type as described in the Dyno Types article. For example, if you migrate your app to a professional dyno, you can easily scale it by running a command telling Heroku to execute a specific number of dynos, each running your web process type.

Scaling an application on Heroku is equivalent to changing the number of dynos that are running. Scale the number of web dynos to zero:

$ heroku ps:scale web=0

Access the app again by hitting refresh on the web tab, or heroku open to open it in a web tab. You will get an error message because you no longer have any web dynos available to serve requests.

Scale it up again:

$ heroku ps:scale web=1

Declare app dependencies

Heroku recognizes an app as a Clojure one by the existence of a project.clj file in the root directory.

The demo app you deployed already has a project.clj, and it looks something like this:

(defproject clojure-getting-started "1.0.0-SNAPSHOT"
  :description "Demo Clojure web app"
  :url "http://clojure-getting-started.herokuapp.com"
  :license {:name "Eclipse Public License v1.0"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.10.0"]
                 [compojure "1.6.1"]
                 [ring/ring-jetty-adapter "1.7.1"]
                 [environ "1.1.0"]]
  :min-lein-version "2.0.0"
  :plugins [[environ/environ.lein "0.3.1"]]
  :hooks [environ.leiningen.hooks]
  :uberjar-name "clojure-getting-started-standalone.jar"
  :profiles {:production {:env {:production true}}})

The project.clj file determines the versions of the dependencies that should be installed with your application, including that of Clojure itself.

Run the app locally

Now start a repl session using Leiningen:

$ lein repl
nREPL server started on port 62103 on host 127.0.0.1 - nrepl://127.0.0.1:62103
REPL-y 0.4.3, nREPL 0.6.0
Clojure 1.10.0
OpenJDK 64-Bit Server VM 12.0.1+12
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=>

At this point you can evaluate any Clojure code in the context of your app, but let’s start by booting the embedded Jetty HTTP server:

user=> (require 'clojure-getting-started.web)
nil
user=> (def server (clojure-getting-started.web/-main))
2014-08-19 16:14:56.170:INFO:oejs.Server:jetty-7.6.8.v20121106
2014-08-19 16:14:56.220:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:5000
#'user/server
user=>

Your app will now be running at http://localhost:5000. Use a web browser to test that it’s working.

Most Clojure users run their repls inside their editor, whether this is Emacs, Vim, Eclipse, etc, but if you haven’t gotten that set up yet it’s simplest just to stick with the command-line lein repl invocations.

You can also start a server using lein run -m clojure-getting-started.web, but this is not recommended during development since you have to restart it every time you make a change. Running from the repl is a lot more convenient.

Push local changes

In this step you’ll learn how to propagate a local change to the application through to Heroku. As an example, you’ll modify the application to add an additional dependency and the code to use it.

Modify project.clj to include a dependency for camel-snake-kebab:

  :dependencies [[org.clojure/clojure "1.10.0"]
                 [compojure "1.6.1"]
                 [ring/ring-jetty-adapter "1.7.1"]
                 [environ "1.1.0"]
                 [camel-snake-kebab "0.4.0"]]

Modify src/clojure_getting_started/web.clj so that it :requires this library:

(ns clojure-getting-started.web
  (:require [compojure.core :refer [defroutes GET PUT POST DELETE ANY]]
            [compojure.handler :refer [site]]
            [compojure.route :as route]
            [clojure.java.io :as io]
            [ring.adapter.jetty :as jetty]
            [environ.core :refer [env]]
            [camel-snake-kebab.core :as kebab]))

Now modify the app routes definition, in the same file, to use it:

(defroutes app
  (GET "/camel" {{input :input} :params}
       {:status 200
        :headers {"Content-Type" "text/plain"}
        :body (kebab/->camelCase input)})
  (GET "/snake" {{input :input} :params}
       {:status 200
        :headers {"Content-Type" "text/plain"}
        :body (kebab/->snake_case input)})
  (GET "/kebab" {{input :input} :params}
       {:status 200
        :headers {"Content-Type" "text/plain"}
        :body (kebab/->kebab-case input)})
  (GET "/" []
       (splash))
  (ANY "*" []
       (route/not-found (slurp (io/resource "404.html")))))

This will add /camel, /snake, and /kebab routes which accept an ?input=... query parameter and change that string into CamelCase, snake_case, or kebab-case, respectively.

Now test locally. Since you’ve changed your dependencies, you’ll need to exit your existing repl session if it’s still open (ctrl-d) and start another one with lein repl:

user=> (require 'clojure-getting-started.web)
2019-08-13 17:06:49.640:INFO::nRepl-session-3c8f54c8-c194-477a-b711-fc3f895a4f78: Logging initialized @6036ms to org.eclipse.jetty.util.log.StdErrLog
nil
user=> (def server (clojure-getting-started.web/-main))
2019-08-13 17:07:07.884:INFO:oejs.Server:nRepl-session-3c8f54c8-c194-477a-b711-fc3f895a4f78: jetty-9.4.12.v20180830; built: 2018-08-30T13:59:14.071Z; git: 27208684755d94a92186989f695db2d7b21ebc51; jvm 12.0.1+12
2019-08-13 17:07:07.920:INFO:oejs.AbstractConnector:nRepl-session-3c8f54c8-c194-477a-b711-fc3f895a4f78: Started ServerConnector@2e98dc13{HTTP/1.1,[http/1.1]}{0.0.0.0:5000}
2019-08-13 17:07:07.921:INFO:oejs.Server:nRepl-session-3c8f54c8-c194-477a-b711-fc3f895a4f78: Started @24317ms
#'user/server

When you navigate to http://localhost:5000/snake?input=HelloWorld, you should see your string in the URL converted to snake_case: hello_world.

Now deploy. Almost every deploy to Heroku follows this same pattern. First, add the modified files to the local git repository:

$ git add .

Now commit the changes to the repository:

$ git commit -m "Add camel-snake-kebab and use it from web.clj."

Now deploy, just as you did previously:

$ git push heroku main

Finally, check that everything is working:

$ curl http://<your-app-name>.herokuapp.com/camel?input=doner-kebab
DonerKebab

Provision add-ons

Add-ons are third-party cloud services that provide out-of-the-box additional services for your application, from persistence through logging to monitoring and more.

By default, Heroku stores 1500 lines of logs from your application. However, it makes the full log stream available as a service - and several add-on providers have written logging services that provide things such as log persistence, search, and email and SMS alerts.

In this step you will provision one of these logging add-ons, Papertrail.

Provision the papertrail logging add-on:

$ heroku addons:create papertrail
Adding papertrail on sharp-rain-871... done, v4 (free)
Welcome to Papertrail. Questions and ideas are welcome (support@papertrailapp.com). Happy logging!
Use `heroku addons:docs papertrail` to view documentation.

The add-on is now deployed and configured for your application. You can list add-ons for your app like so:

$ heroku addons

To see this particular add-on in action, visit your application’s Heroku URL a few times. Each visit will generate more log messages, which should now get routed to the papertrail add-on. Visit the papertrail console to see the log messages:

$ heroku addons:open papertrail

Your browser will open up a Papertrail web console, showing the latest log events. The interface lets you search and set up alerts:

Screenshot of console with log messages

Start a repl on a dyno

You can run a command, typically scripts and applications that are part of your app, in a one-off dyno using the heroku run command. It can also be used to launch a REPL process attached to your local terminal for experimenting in your app’s environment:

$ heroku run lein repl
Running `lein repl` attached to terminal... up, run.1459
user=>

If you receive an error, Error connecting to process, then you may need to configure your firewall.

The REPL functions just like a local lein repl, but it’s remote.

You will be be able to run the following:

user=> (require '[camel-snake-kebab.core :as kebab])
nil
user=> (kebab/->kebab-case "JustSomeStuff")
"just-some-stuff"

To get a real feel for how dynos work, you can create another one-off dyno and run the bash command, which opens up a shell on that dyno. You can then execute commands there. Each dyno has its own ephemeral filespace, populated with your app and its dependencies - once the command completes (in this case, bash), the dyno is removed.

$ heroku run bash
Running `bash` attached to terminal... up, run.3052
~ $ ls
Procfile  README.md  project.clj  resources  src  target  test

Don’t forget to hit ctrl-d (or exit) to exit the shell and terminate the dyno.

Define config vars

Heroku lets you externalise configuration - storing data such as encryption keys or external resource addresses in config vars.

At runtime, config vars are exposed as environment variables to the application. The environ library exposes this in a convenient way, though of course you can use raw System/getenv calls if you prefer.

Let’s add links from the splash page, defined in src/clojure_getting_started/web.clj to sample conversions:

(def sample (env :sample "sample-string-thing"))

(defn splash []
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (for [kind ["camel" "snake" "kebab"]]
           (format "<a href=\"/%s?input=%s\">%s %s</a><br />"
                   kind sample kind sample))})

The sample var contains the string that will be used in the sample links. Environ handles translating from the SAMPLE environment variable to (env :sample) with an optional default value.

To set the config var on Heroku, execute the following:

$ heroku config:set SAMPLE=this_one_started_as_snake_case
Setting config vars and restarting sharp-rain-871... done, v10
SAMPLE: this_one_started_as_snake_case

View the config vars that are set using heroku config:

$ heroku config
== sharp-rain-871 Config Vars
PAPERTRAIL_API_TOKEN: [REDACTED]
SAMPLE: this_one_started_as_snake_case

Deploy your changed application to Heroku to see this in action.

Provision a database

Adding a database to complete this tutorial counts towards your usage. Delete your database as soon as you’re done to control costs. Learn about our low-cost plans. Eligible students can apply for platform credits through our Heroku for GitHub Students program.

The add-on marketplace has a large number of data stores, from Redis and MongoDB providers, to Postgres and MySQL. In this step you will add a Heroku Mini Postgres Starter Tier dev database to your app.

Add the database:

$ heroku addons:create heroku-postgresql:mini
Adding heroku-postgresql:mini... done, v3 (mini)

This creates a database, and sets a DATABASE_URL environment variable (you can check by running heroku config).

Edit your project.clj file to add the java.jdbc library and postgresql driver to your dependencies:

:dependencies [[org.clojure/clojure "1.10.0"]
                 [compojure "1.6.1"]
                 [ring/ring-jetty-adapter "1.7.1"]
                 [environ "1.1.0"]
                 [org.clojure/java.jdbc "0.6.1"]
                 [org.postgresql/postgresql "9.4.1211"]
                 [camel-snake-kebab "0.4.0"]]

Now edit your web.clj file to load this library. Add [clojure.java.jdbc :as db] to the require section of ns. Next, read and write to the DB:

(defn splash []
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (concat (for [kind ["camel" "snake" "kebab"]]
                   (format "<a href=\"/%s?input=%s\">%s %s</a><br />"
                           kind sample kind sample))
                 ["<hr /><ul>"]
                 (for [s (db/query (env :database-url)
                                   ["select content from sayings"])]
                   (format "<li>%s</li>" (:content s)))
                 ["</ul>"])})

(defn record [input]
  (db/insert! (env :database-url "postgres://localhost:5432/kebabs")
              :sayings {:content input}))

(defroutes app
  (GET "/camel" {{input :input} :params}
       (record input)
       {:status 200
        :headers {"Content-Type" "text/plain"}
        :body (kebab/->camelCase input)})
  (GET "/snake" {{input :input} :params}
       (record input)
       {:status 200
        :headers {"Content-Type" "text/plain"}
        :body (kebab/->snake_case input)})
  (GET "/kebab" {{input :input} :params}
       (record input)
       {:status 200
        :headers {"Content-Type" "text/plain"}
        :body (kebab/->kebab-case input)})
  (GET "/" []
       (splash))
  (ANY "*" []
       (route/not-found (slurp (io/resource "404.html")))))

This ensures that all invocations will be recorded and listed in the sayings table. They will be shown on the splash page.

Deploy this to Heroku. If you access / you’ll get a blank page–to get the real story you’ll need to run heroku logs. There’s a big stack trace, but at the top it says:

org.postgresql.util.PSQLException: ERROR: relation "sayings" does not exist|  Position: 21

This means we haven’t gotten around to creating the table yet. Note that in a real-world application we would have ring middleware set up to catch exceptions and either display the stack trace in the browser (during development) or show a user-friendly error message (in production), but that is outside the scope of this article.

Assuming that you have Postgres installed locally, use the heroku pg:psql command to connect to the remote database, create a table and insert a row:

$ heroku pg:psql
--> Connecting to postgresql-symmetrical-27482
psql (11.4, server 11.5 (Ubuntu 11.5-1.pgdg16.04+1))
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.
=> create table sayings (id serial primary key, content text);
CREATE TABLE
=> insert into sayings values (1, 'HelloComputer');
INSERT 0 1
=> \q

Now seed it with a few sample invocations:

$ curl http://sharp-rain-871.herokuapp.com/camel?input=doner-kebab
doner_kebab
$ curl http://sharp-rain-871.herokuapp.com/kebab?input=snake_like_charms
snake-like-charms

When you access your app’s / route, you will see something like this below the sample links:

  • HelloComputer
  • doner-kebab
  • snake_like_charms

Read more about Heroku PostgreSQL.

A similar technique can be used to install MongoDB or Redis add-ons.

Next steps

You now know how to deploy an app, change its configuration, view logs, scale, and attach add-ons.

Here’s some recommended reading. The first, an article, will give you a firmer understanding of the basics. The second is a pointer to the main Clojure category here on Dev Center:

  • Read How Heroku Works for a technical overview of the concepts you’ll encounter while writing, configuring, deploying and running applications.
  • Read Deploying Clojure Apps on Heroku to understand how to take an existing Clojure app and deploy it to Heroku.
  • Visit the Clojure category to learn more about developing and deploying Clojure applications.
  • Learn more about the Heroku developer experience and CI/CD features in the Heroku Enterprise Developer Learning Journey.

Information & Support

  • Getting Started
  • Documentation
  • Changelog
  • Compliance Center
  • Training & Education
  • Blog
  • Podcasts
  • Support Channels
  • Status

Language Reference

  • Node.js
  • Ruby
  • Java
  • PHP
  • Python
  • Go
  • Scala
  • Clojure

Other Resources

  • Careers
  • Elements
  • Products
  • Pricing

Subscribe to our monthly newsletter

Your email address:

  • RSS
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku Blog
    • Heroku News Blog
    • Heroku Engineering Blog
  • Heroku Podcasts
  • Twitter
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku
    • Heroku Status
  • Facebook
  • Instagram
  • Github
  • LinkedIn
  • YouTube
Heroku is acompany

 © Salesforce.com

  • heroku.com
  • Terms of Service
  • Privacy
  • Cookies
  • Cookie Preferences