Building, Packaging and Deployment

45.1 tools.build: Uberjars

The main namespace needs (:gen-class) in its ns form and a -main function for AOT/uberjar entry points.

;; build.clj
(ns build
  (:require [clojure.tools.build.api :as b]))

(def lib 'myorg/myapp)
(def version "1.0.0")
(def class-dir "target/classes")
(def basis (delay (b/create-basis {:project "deps.edn"})))
(def uber-file (format "target/%s-%s-standalone.jar" (name lib) version))

(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 '[myapp.core] :class-dir class-dir})
  (b/uber {:class-dir class-dir :uber-file uber-file :basis @basis :main 'myapp.core}))
clj -T:build uber
java -jar target/myapp-1.0.0-standalone.jar

45.2 Docker and Native Images

GraalVM native-image compiles to a native binary (fast startup, low memory). It needs reflection configuration; see clj-easy/graal-build-time. Startup can also be improved with AOT compilation, Class Data Sharing (CDS), and the JDK's AppCDS/Leyden features.

FROM clojure:temurin-21-tools-deps AS build
WORKDIR /app
COPY deps.edn build.clj ./
RUN clojure -P -T:build                   # pre-download dependencies
COPY src src
COPY resources resources
RUN clojure -T:build uber

FROM eclipse-temurin:21-jre
COPY --from=build /app/target/*-standalone.jar /app/app.jar
EXPOSE 3000
CMD ["java", "-XX:+UseZGC", "-jar", "/app/app.jar"]

45.3 Libraries and CI

Publish libraries to Clojars (deps-deploy), or consume them directly as git dependencies.

CI: GitHub Actions with DeLaGuardo/setup-clojure, cache ~/.m2 and ~/.gitlibs, run clj-kondo, cljfmt check, tests, and the build.