Deployment
An external servlet container
Deploy to an external servlet container by skipping start and mapping AppServlet yourself:
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.addServlet(new ServletHolder(new AppServlet(app)), "/*");
AppServlet is a standard servlet, so it runs on any container (Tomcat and others), not just Jetty.
An embedded Tomcat that app.start(port) runs itself is a different thing, and lives in spider-silk-tomcat: see Tomcat, and Undertow for the third option.
Deploying to a container you did not start, exclude the bundled Jetty:
implementation('io.github.benelog.spidersilk:spider-silk-core') {
exclude group: 'org.eclipse.jetty.ee10'
}
Precompiled templates
jte compiles templates at runtime by default, which requires a JDK on the deployment machine. A production build precompiles instead: the jte Gradle plugin turns each template into a Java class at build time, so the jar renders compiled classes and a template that does not compile fails the build rather than the request. The plugin configuration and the matching constructor are in Templates, alongside the development-mode counterpart; a native image makes this mode a requirement rather than an optimization, since a native binary carries no compiler.
A container image, without Docker
Jib builds a container image from the Gradle model alone, so the build machine needs no Docker daemon: gradle :example-flashcard:jibBuildTar writes build/jib-image.tar, ready for any Docker host to load.
The example’s configuration is the plugin, a JRE base, and the port:
plugins {
id 'com.google.cloud.tools.jib' version '3.5.4'
}
jib {
from {
image = 'eclipse-temurin:21-jre'
}
to {
image = 'ghcr.io/benelog/spider-silk-flashcard'
}
container {
ports = ['8080']
}
}
Jib layers the image the way the build is structured — dependencies, project modules, resources, classes — so an edit to the application code rewrites only the small layers on top, and it takes the entrypoint from the application block’s mainClass.
A JRE base suffices because the templates ride along as the precompiled classes from the section above; nothing in the image compiles at runtime.
One wrinkle: Jib checks the app’s targetCompatibility against the base image’s Java version, and a build that targets 21 through options.release alone must restate it — java { targetCompatibility = JavaVersion.VERSION_21 } — or the check reads the toolchain’s version and refuses the JRE 21 base.
gradle :example-flashcard:jibBuildTar
# then, on a machine that runs containers:
docker load < example-flashcard/build/jib-image.tar
docker run -p 8080:8080 ghcr.io/benelog/spider-silk-flashcard
The tar is only the daemonless route: gradle jib pushes the same image straight to the registry given credentials, and gradle jibDockerBuild loads it into a local daemon where one exists.
H2 keeps its database file under the process home, /root/db in this image, so the data lives and dies with the container unless a volume is mounted there.
The same build containerizes the GraalVM binary instead when asked: -Pnative swaps the base image and the layers, and the native image chapter shows what changes.