Example: Spring PetClinic¶
To run PetClinic on your laptop, start with Local Kubernetes. That guide uses a disposable kind cluster, without changing Docker Desktop's existing Kubernetes cluster. This page is the contributor-oriented integration-test and layering reference.
Spring PetClinic proves the Brewlet model with a real, dependency-heavy Spring Boot application rather than the dependency-free demo fixture.
The example spans monorepo components:
| Piece | Owner |
|---|---|
| Pinned upstream build and layering scripts | integration-tests/fixtures/spring-petclinic |
| End-to-end orchestration | integration-tests/e2e/tier7-petclinic.sh |
| CLI and shim | core/ |
JavaApplication descriptor and operator |
kubernetes/ |
| Architecture contract | specs/ |
Nothing about PetClinic is special to Brewlet. Its ordinary Spring Boot JAR is
published as an OCI artifact and launched with java -jar on a node-resident JDK.
Run the complete proof¶
Clone the monorepo:
git clone https://github.com/microsoft/brewlet.git
cd brewlet
export JAVA_HOME="$HOME/.sdkman/candidates/java/current" # any full JDK 21+
integration-tests/e2e/run.sh --reset --tier 7
For external component checkouts, set BREWLET_CORE_DIR and
BREWLET_KUBERNETES_DIR explicitly. The harness does not change component
branches.
Tier 7:
- Builds the CLI from
core/. - Uses the integration-test fixture to clone a pinned Spring PetClinic revision and build its fat JAR.
- Pushes and inspects only that JAR as an OCI artifact.
- Runs it through the core shim and
runcunder real cgroup limits when Docker is available. - Builds the operator from
kubernetes/and reconciles the PetClinicJavaApplicationwhen a cluster is reachable. - Splits the application into deterministic classpath layers and verifies that a business-code rebuild reuses the dependency layer.
Unavailable optional prerequisites produce explicit skips. Build or runtime failures in an exercised path fail the tier.
Build and inspect the fixture manually¶
From the repository root:
export BREWLET_CORE_DIR="$PWD/core"
export FIXTURE_DIR="$PWD/integration-tests/fixtures/spring-petclinic"
export WORK_DIR="$PWD/.brewlet-petclinic"
mkdir -p "$WORK_DIR"
"$FIXTURE_DIR/build.sh"
(cd "$BREWLET_CORE_DIR" && go build -o "$WORK_DIR/brewlet" ./cmd/brewlet)
"$WORK_DIR/brewlet" push \
"$FIXTURE_DIR/target/spring-petclinic.jar" \
demo/petclinic:1.0.0 \
--store "$WORK_DIR/oci" \
--format=artifact
"$WORK_DIR/brewlet" inspect demo/petclinic:1.0.0 \
--store "$WORK_DIR/oci"
The launch config reports entry.mode: jar and
mainJar: spring-petclinic.jar. JDK selection, ports, resource limits, and JVM
tuning remain deployment concerns and are not embedded in the artifact.
Deploy with JavaApplication¶
Publish to a registry reachable by your cluster, then use the descriptor in
kubernetes/:
"$WORK_DIR/brewlet" push \
"$FIXTURE_DIR/target/spring-petclinic.jar" \
registry.example.com/demo/petclinic:1.0.0
kubectl apply -f kubernetes/deploy/petclinic-javaapplication.yaml
kubectl get javaapplication,deploy,svc -n petclinic
Update the placeholder registry and image-index digest in the descriptor from
the index: sha256:… value printed by brewlet push. Kubernetes Brewlet
workloads require that digest-pinned reference. The operator reconciles the
resource into a Deployment with runtimeClassName: brewlet, a Service, and
optional autoscaling.
See the
petclinic-javaapplication.yaml
source for replicas, cgroup limits, actuator probes, the H2 profile, and
autoscaling.
Layered classpath delivery¶
After configuring the Maven plugin from Central,
you can prepare a standard Boot executable JAR directly with
-Dbrewlet.layered=true: it relocates application classes/resources, preserves
packaged library bytes and classpath order, and shares that preparation with
AppCDS training. See building and publishing.
The fixture-based CLI walkthrough below remains a separate explicit split.
A fat JAR is one large blob, so any code change produces a new blob. The fixture's
layered-build.sh
maps Spring Boot's structure onto Brewlet's framework-neutral layers:
- application classes and resources become a thin application JAR;
- release dependencies become a deterministic classpath tar;
- snapshot dependencies, when present, become a separate volatile tar; and
- launch mode changes from
java -jartojava -cp app.jar:lib/* <MainClass>.
Run the split:
Then publish the generated files:
"$WORK_DIR/brewlet" push \
"$FIXTURE_DIR/target/layered/spring-petclinic-app.jar" \
demo/petclinic-layered:1.0.0 \
--store "$WORK_DIR/oci" \
--format=artifact \
--config "$FIXTURE_DIR/target/layered/jvm-config.json" \
--classpath-layer "$FIXTURE_DIR/target/layered/deps-dependencies.tar"
The dependency tar uses sorted entries and normalized timestamps. An unchanged dependency set therefore retains the same digest across application rebuilds and is not transferred again. Tier 7 validates this property and runs the layered artifact through the shim/runc path.
This mapping does not require Brewlet to parse Spring Boot's layers.idx.
Any framework or build that can provide compiled application classes and a
directory of dependency JARs can produce the same generic Brewlet layout.