Building and Deploying Applications
Prerequisites: Projects with deps.edn and the Clojure CLI
A Clojure service is usually deployed as an uberjar: one jar with
your code (AOT-compiled) and every dependency, run with java -jar.
build.clj with tools.build
;; illustrative — build.clj, run with: clj -T:build uber
(ns build
(:require [clojure.tools.build.api :as b]))
(def class-dir "target/classes")
(def uber-file "target/inventory.jar")
(def basis (delay (b/create-basis {:project "deps.edn"})))
(defn clean [_]
(b/delete {:path "target"}))
(defn uber [_]
(clean nil)
(b/copy-dir {:src-dirs ["src" "resources"] :target-dir class-dir})
(b/compile-clj {:basis @basis :ns-compile '[inventory.core] :class-dir class-dir})
(b/uber {:class-dir class-dir :uber-file uber-file :basis @basis
:main 'inventory.core}))
The main namespace needs (:gen-class) in its ns form and a -main
function. Then:
clj -T:build uber
java -jar target/inventory.jar
Configuration
;; illustrative — resources/config.edn, read with aero or clojure.edn
{:port #long #or [#env PORT 3000]
:db {:jdbc-url #env DATABASE_URL}
:log-level #keyword #or [#env LOG_LEVEL "info"]}
Keep configuration in EDN and secrets in environment variables. The
aero library adds the #env/#or tags shown here; with plain
clojure.edn you'd read the environment in code instead.
Containers
# illustrative — Dockerfile
FROM clojure:temurin-21-tools-deps AS build
WORKDIR /app
COPY deps.edn build.clj ./
RUN clj -P -T:build # download dependencies (cached layer)
COPY src src
COPY resources resources
RUN clj -T:build uber
FROM eclipse-temurin:21-jre
COPY --from=build /app/target/inventory.jar /app/inventory.jar
USER 10001
EXPOSE 3000
CMD ["java", "-XX:MaxRAMPercentage=75", "-jar", "/app/inventory.jar"]
Running in production
- Logging —
clojure.tools.loggingover SLF4J/Logback, or mulog/telemere for structured event logging. - Lifecycle — start and stop components (HTTP server, DB pool) with Integrant or Component, and add a shutdown hook.
- Observability — JVM metrics and tracing via standard Java agents (OpenTelemetry works unchanged).
- REPL in production — a socket REPL (
-Dclojure.server.repl=...) lets you inspect a live system. Powerful but dangerous: bind it to localhost only and guard access carefully. - Native images — GraalVM
native-imagecan compile many Clojure apps to fast-starting binaries, useful for CLIs and serverless.