I built a tool to solve a problem I kept hitting: deploying Clojure apps without requiring Java on the target machine.23:20:00 [3/101]
The usual answer is GraalVM native-image, but in practice it means dealing with reflection configs, library incompatibilities, long build times, and a complex toolchain. For many projects it's more friction than it's worth.
clj-pack takes a different approach: it bundles a minimal JVM runtime (via jlink) with your uberjar into a single executable. The result is a binary that runs anywhere with zero external dependencies and full JVM compatibility — no reflection configs, no unsupported libraries, your app runs exactly as it does in development.
clj-pack build --input ./my-project --output ./dist/my-app
./dist/my-app # no Java needed
How it works:
Detects your build system (deps.edn or project.clj)
Compiles the uberjar
Downloads a JDK from Adoptium (cached locally)
Uses jdeps + jlink to create a minimal runtime (~30-50 MB)
Packs everything into a single binary
The binary extracts on first run (cached by content hash), subsequent runs are instant.
Trade-off is honest: binaries are slightly larger than GraalVM output (~30-50 MB vs ~20-40 MB), and first execution has extraction overhead. But you get full compatibility and a simple build process in return.
Written in Rust, supports Linux and macOS (x64/aarch64).
I built a tool to solve a problem I kept hitting: deploying Clojure apps without requiring Java on the target machine.23:20:00 [3/101]
The usual answer is GraalVM native-image, but in practice it means dealing with reflection configs, library incompatibilities, long build times, and a complex toolchain. For many projects it's more friction than it's worth.
clj-pack takes a different approach: it bundles a minimal JVM runtime (via jlink) with your uberjar into a single executable. The result is a binary that runs anywhere with zero external dependencies and full JVM compatibility — no reflection configs, no unsupported libraries, your app runs exactly as it does in development.
clj-pack build --input ./my-project --output ./dist/my-app ./dist/my-app # no Java needed How it works:
Detects your build system (deps.edn or project.clj)
Compiles the uberjar
Downloads a JDK from Adoptium (cached locally)
Uses jdeps + jlink to create a minimal runtime (~30-50 MB)
Packs everything into a single binary
The binary extracts on first run (cached by content hash), subsequent runs are instant.
Trade-off is honest: binaries are slightly larger than GraalVM output (~30-50 MB vs ~20-40 MB), and first execution has extraction overhead. But you get full compatibility and a simple build process in return.
Written in Rust, supports Linux and macOS (x64/aarch64).
Feedback and contributions welcome
Very nice approach.