GraalVM Docker images for running RESTHeart and other headless Java services. Maintained by SoftInstigate.
RESTHeart runs on GraalVM in production. Official GraalVM images weigh ~878 MB and ship with GUI libraries, native-image tools, and debug symbols that a headless server never uses.
This repository strips those components during the Docker build, producing images at ~285 MB (68% smaller). The distroless variant, based on Google's distroless, removes the shell and package manager entirely, reducing the CVE surface by ~90%. Both images are built for amd64 and arm64 and published to Docker Hub as softinstigate/graalvm.
# Run a JAR (distroless, no shell)
docker run --rm -v "$PWD":/opt/app \
softinstigate/graalvm:25 \
-jar /opt/app/restheart.jar
# Interactive debugging (shell variant)
docker run -it softinstigate/graalvm:25-shell /bin/shNo shell. No package manager. Runs as non-root (UID 65532). Based on Google's distroless base image. Cannot docker exec into the container. Use remote JDWP debugging or the shell variant for interactive access.
docker run --rm -v "$PWD":/opt/app \
softinstigate/graalvm:25 \
-jar /opt/app/app.jarIncludes /bin/sh for interactive debugging and docker exec. Based on debian:stable-slim.
docker run -it softinstigate/graalvm:25-shell /bin/shAll tags support amd64 and arm64 automatically.
| Variant | Tags |
|---|---|
| Distroless | latest, 25, 25.1, 25.1.3 |
| Shell | 25-shell, 25.1-shell, 25.1.3-shell |
- GraalVM JDK 25 (HotSpot + GraalVM JIT)
- All Java standard libraries
- Headless mode (
JAVA_OPTS="-Djava.awt.headless=true") - HTTPS/TLS support (CA certificates)
Server-side Java does not need GUI toolkits, native-image build tools, or static libraries. The build strips ~593 MB of components:
| Removed | Savings |
|---|---|
| GUI libraries (AWT, Swing, JavaFX) | ~20 MB |
| Native-image build tools | ~37 MB |
| Static libraries | ~183 MB |
| SubstrateVM components | ~64 MB |
| jmods | ~110 MB |
| Samples/demos | ~60 MB |
See Docker Build Architecture for the full rationale.
FROM softinstigate/graalvm:25
COPY app.jar /opt/app/
CMD ["-jar", "/opt/app/app.jar"]Docker Compose:
services:
app:
image: softinstigate/graalvm:25
command: ["-jar", "/opt/app/app.jar"]
volumes:
- ./:/opt/app- Docker Desktop or Docker Engine with Buildx enabled
- (Multi-arch builds) QEMU emulation. CI sets this up automatically. For local builds:
docker run --privileged --rm tonistiigi/binfmt --install all
# Local, current architecture only
docker build -f Dockerfile.distroless -t myimage:25 . # distroless
docker build -t myimage:25-shell . # shell
# Multi-arch, builds and pushes both variants
./build-multiarch.sh
# CI/CD, automatic on git tag push
git tag 25.1.3-graalce && git push origin 25.1.3-graalceThe GRAALVM_VERSION is defined once in .github/workflows/docker-image.yml and passed as a build arg to both Dockerfiles.
Distroless has no shell. Use remote JDWP:
docker run -p 5005:5005 softinstigate/graalvm:25 \
-agentlib:jdwp=transport=dt_socket,server=y,address=*:5005 \
-jar /opt/app/app.jarOr switch to the shell variant for interactive access.
| Property | Distroless | Shell |
|---|---|---|
| Shell available | No | Yes |
| Package manager | No | Yes |
| Runs as | Non-root (65532) | Root (configurable) |
docker exec possible |
No | Yes |
| CVE surface | Minimal | Standard Debian |
Works on Intel/AMD (x86_64), Apple Silicon (M1/M2/M3/M4), AWS Graviton, and ARM servers. Docker pulls the correct architecture for the host automatically.
- Set
GRAALVM_VERSION,GRAALVM_RELEASE_TAG, andGRAALVM_ARCHIVE_VERSIONin.github/workflows/docker-image.yml - Update the same values in
DockerfileandDockerfile.distroless - Update the version in this
README.md - Test locally:
docker build -f Dockerfile.distroless . - Tag and push:
git tag 25.1.3-graalce && git push origin 25.1.3-graalce