This is the official Heroku buildpack for Clojure apps. It uses Leiningen or the Clojure CLI tools.
Note that you don't have to do anything special to use this buildpack with Clojure apps on Heroku; it will be used by default for all projects containing a project.clj or deps.edn file, though it may be an older revision than what you're currently looking at.
The buildpack will detect your app as Clojure if it has a
project.clj
or deps.edn
file in the root. If there are both files, it will
use Leiningen.
If you use the
clojure-maven-plugin,
the standard Java buildpack
should work instead.
For more information about using Clojure and buildpacks on Heroku, see these Dev Center articles:
- Getting Started with Clojure on Heroku
- Heroku Clojure Support
- Building a Database-Backed Clojure Web Application
- Database Connection Pooling with Clojure
- Live-Debugging Remote Clojure Apps with Drawbridge
- WebSockets on Heroku with Clojure and Immutant
- Queuing in Clojure with Langohr and RabbitMQ
Example usage for an app already stored in git:
$ tree
|-- Procfile
|-- project.clj
|-- README
`-- src
`-- sample
`-- core.clj
$ heroku create
...
$ git push heroku main
...
remote: -----> Fetching custom tar buildpack... done
remote: -----> Clojure (Leiningen 2) app detected
remote: -----> Installing OpenJDK 1.8...done
remote: -----> Installing Leiningen
remote: Downloading: leiningen-2.5.2-standalone.jar
remote: Writing: lein script
remote: -----> Building with Leiningen
remote: Running: lein uberjar
remote: Created /tmp/build_37f1ae84b9f8b63c3ddef2a4b691ef41/target/clojure-getting-started-1.0.0-SNAPSHOT.jar
remote: Created /tmp/build_37f1ae84b9f8b63c3ddef2a4b691ef41/target/clojure-getting-started-standalone.jar
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing... done, 53.6MB
remote: -----> Launching... done, v5
remote: https://gentle-water-6857.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy.... done.
The default version of Clojure CLI tools installed is 1.10.3.1029.
You can request something else by setting the config param
CLOJURE_CLI_VERSION
to something else.
Your Procfile
should declare what process types make up your app.
Generally this is done with :exec-fn
aliases in your deps.edn
file.
For example, if your deps.edn
has something like this:
{:aliases
{:web
{:exec-fn my.ns/run-web-server}}}
...then your Procfile
should look like this:
web: clojure -X:web
If your project is missing a Procfile
the one above will generated
automatically on deploy. So you can just setup a :web
alias with the fn you
want to run in its :exec-fn
in your deps.edn
file.
Leiningen 1.7.1 will be used by default, but if you have
:min-lein-version "2.0.0"
in project.clj (highly recommended) then
the latest Leiningen 2.x release will be used instead.
Your Procfile
should declare what process types make up your
app. Often in development Leiningen projects are launched using lein run -m my.project.namespace
, but this is not recommended in
production because it leaves Leiningen running in addition to your
project's process. It also uses profiles that are intended for
development, which can let test libraries and test configuration sneak
into production.
In order to ensure consistent builds, normally values set with heroku config:add ...
(other than LEIN_USERNAME
, LEIN_PASSWORD
, and
LEIN_PASSPHRASE
) will not be visible at compile time. To expose more
to the compilation process, set a BUILD_CONFIG_ALLOWLIST
config var
containing a space-delimited list of config var names. Note that this
can result in unpredictable behaviour since changing your app's config
does not result in a rebuild of your app. So it's easy to get into a
situation where your build is broken, but you don't notice it until
later when you push. For this reason it's recommended to take care
with this feature and always push after changing a allowlisted config
value.
If your project.clj
contains an :uberjar-name
setting, then
lein uberjar
will run during deploys. If you do this, your Procfile
entries should consist of just java
invocations.
If your main namespace doesn't have a :gen-class
then you can use
clojure.main
as your entry point and indicate your app's main
namespace using the -m
argument in your Procfile
:
web: java -cp target/myproject-standalone.jar clojure.main -m myproject.web
If you have custom settings you would like to only apply during build,
you can place them in an :uberjar
profile. This can be useful to use
AOT-compiled classes in production but not during development where
they can cause reloading issues:
:profiles {:uberjar {:main myproject.web, :aot :all}}
If you need Leiningen in a heroku run
session, it will be downloaded
on-demand.
Note that if you use Leiningen features which affect runtime like
:jvm-opts
, extraction of native dependencies, or :java-agents
,
then you'll need to do a little extra work to ensure your Procfile's
java
invocation includes these things. In these cases it might be
simpler to use Leiningen at runtime instead.
Instead of putting a direct java
invocation into your Procfile, you
can have Leiningen handle launching your app. If you do this, be sure
to use the trampoline
and with-profile
tasks. Trampolining will
cause Leiningen to calculate the classpath and code to run for your
project, then exit and execute your project's JVM, while
with-profile
will omit development profiles:
web: lein with-profile production trampoline run -m myapp.web
Including Leiningen in your slug will add about ten megabytes to its size and will add a second or two of overhead to your app's boot time.
If neither of these options get you quite what you need, you can check
in your own executable bin/build
script into your app's repo and it
will be run instead of compile
or uberjar
after setting up Leiningen.
The buildpack will check for a bin/lein
script in the repo, and run it instead
of the default lein
command. This allows you to control the exact version of
Leiningen used to build the app.
By default you will get OpenJDK 1.8. To use a different version, you
can commit a system.properties
file to your app.
$ echo "java.runtime.version=1.7" > system.properties
$ git add system.properties
$ git commit -m "JDK 7"
To change this buildpack, fork it on GitHub. Push up changes to your
fork, then create a test app with --buildpack YOUR_GITHUB_URL
and
push to it. If you already have an existing app you may use
heroku config:add BUILDPACK_URL=YOUR_GITHUB_URL
instead.
For example, you could adapt it to generate a tarball at build time.
Open bin/compile
in your editor, and replace the block labeled
"Calculate build command" with something like this:
echo "-----> Generating tar with Leiningen:"
echo " Running: lein tar"
cd $BUILD_DIR
PATH=.lein/bin:/usr/local/bin:/usr/bin:/bin JAVA_OPTS="-Xmx500m -Duser.home=$BUILD_DIR" lein tar 2>&1 | sed -u 's/^/ /'
if [ "${PIPESTATUS[*]}" != "0 0" ]; then
echo " ! Failed to create tar with Leiningen"
exit 1
fi
Commit and push the changes to your buildpack to your GitHub fork, then push your sample app to Heroku to test. The output should include:
-----> Generating tar with Leiningen:
If it's something other users would find useful, pull requests are welcome.
Tests can be run and debugged locally by using the Circle CI CLI.
For example, to run Hatchet tests on heroku-18
run:
$ circleci local execute --job hatchet-heroku-18 \
--env HEROKU_API_USER=$(heroku whoami) \
--env HEROKU_API_KEY=$(heroku auth:token)
Available jobs are defined in .circleci/config.yml.
This command uses the credentials from your local heroku
configuration. This means your account will be billed for any
cost these tests incur. Proceed with caution.
To see what the buildpack has produced, do heroku run bash
and you
will be logged into an environment with your compiled app available.
From there you can explore the filesystem and run lein
commands.