Leiningen: Split an uberjar into dependencies.jar and app.jar (to optimize Docker layers and AWS Lambda functions)
I want to split my application uberjar into a separate JAR with only the dependencies and a JAR with only the application code so that I can upload them as separate “layers” and thus leverage layer caching. While my code changes frequently and is tiny, the dependencies change rarely and are much bigger. If I can add them as a separate Docker layer or AWS Lambda layer then this can be cached on the server and reused when I upload a new version - saving time, bandwidth, and money.
I was able to figure how how to do that with Leiningen thanks to the invaluable help of mikerod
at the #leiningen channel:
project.clj
(defproject myapp "0.1.0-SNAPSHOT"
:dependencies [...]
:target-path "target/%s/" ;; (1)
:auto-clean false ;; (2)
:profiles {:jar {:jar-name "myapp.jar" ;; (3)
:main minbedrift-uav.core ; actually not needed
:aot [minbedrift-uav.core]}
:uberjar {:uberjar-name "myapp-dependencies.jar" ;; (4)
:uberjar-exclusions [#"README.md" "project.clj"]
:source-paths ^:replace []
:resource-paths ^:replace []}}
:aliases {"jar" ["with-profile" "jar" "jar"]}) ;; (5)
Highlights
Store jar and uberjar build artefacts separately so that they do not mess up with each other
Disable
:auto-clean
so that building the uberjar won’t delete the jarMove the
:main
and:aot
from the top level into the new:jar
profile so that they are not run during uberjar-ing (it seems that:main
cannot be overriden tonil
in:uberjar
; and withnil
we cannot apply^:replace
)Configure the uberjar to have empty (re)source paths and thus not include the application code; here
^:replace
is crucialAdd a convenience alias so that
lein jar
will automatically activate thejar
profile
Dockerfile
FROM azul/zulu-openjdk-alpine:11-jre
WORKDIR /app
COPY /target/uberjar/myapp-dependencies.jar /app/
COPY /target/jar/myapp.jar /app/
CMD java -Dfile.encoding=UTF-8 -cp myapp-dependencies.jar:myapp.jar myapp.core
The crucial thing here is to include the dependencies before the application code.
Building
lein do clean, jar, uberjar
docker build -t myapp .
(Notice that we have to clean
manually now.)
Alternatives
Alternatively we could use e.g. the lein-libdir
plugin to copy all dependencies into a directory and include their jars individually (e.g. COPY /lib/*.jar /app/
)