This episode covers deployment in the cloud: optimizing the Dockerfile with multi-stage builds, Spring Boot Native Image with GraalVM and AOT, Kubernetes integration with readiness probes, and deployment to AWS, GCP, Azure, and PaaS platforms.

Running an application on a laptop isn't enough — it has to run in the cloud with lightweight containers, fast startup, and the right probes for orchestration to work. Episode 20 covers cloud-native deployment end to end.
You'll optimize the Dockerfile, learn about native images with GraalVM, configure Kubernetes probes correctly, and review cloud platform options for deployment.
A poorly built container can contain build tools, source code, and bloated layers. Multi-stage builds solve this: the first stage builds the application with a JDK and Maven, the second stage only copies the jar into a slim runtime image:
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /app
COPY .mvn .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline
COPY src ./src
RUN ./mvnw clean package -DskipTests
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=builder /app/target/belajar-spring-boot.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "app.jar"]The builder stage uses a JDK to compile; the runtime stage only carries a JRE and the jar. Notice the dependency:go-offline step before COPY src — this leverages Docker's layer cache so dependencies aren't re-downloaded when the source changes.
-XX:MaxRAMPercentage flag so the heap adjusts dynamically to the container's memory limit.The JVM starts an application in seconds; a native image starts in milliseconds with far less memory. GraalVM compiles your Java application into a native executable — Java code is translated to machine code at build time.
./mvnw -Pnative native:compile
./target/belajar-spring-bootThe command ./mvnw -Pnative native:compile produces a native executable in the target folder. The result: the application runs without a JVM, making it a good fit for serverless and frequently scaled instances.
Spring AOT (Ahead-of-Time) processes the application at build time — scanning beans, configuration, and reflection — so the native image knows what to include. However, not every library is compatible with native images, and build time is much longer. For applications that need ultra-fast startup with an acceptable trade-off, a native image is a strong choice.
Kubernetes uses probes to know the application's condition. The Spring Boot Actuator from episode 9 is the source:
spec:
containers:
- name: belajar-spring-boot
image: belajar-spring-boot:1.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080readinessProbe decides when a pod receives traffic, livenessProbe decides when a pod is restarted because it's unhealthy, and startupProbe (for slow-starting applications) delays both probes above. The Actuator provides these three endpoints separately — make sure management.endpoint.health.probes.enabled=true.
Define a Deployment so Kubernetes manages the rollout:
apiVersion: apps/v1
kind: Deployment
metadata:
name: belajar-spring-boot
spec:
replicas: 3
selector:
matchLabels:
app: belajar-spring-boot
template:
metadata:
labels:
app: belajar-spring-boot
spec:
containers:
- name: belajar-spring-boot
image: belajar-spring-boot:1.0
imagePullPolicy: IfNotPresentUse Helm charts to manage this template in a parameterizable way. Rollout strategies — canary and blue-green — will be covered in depth in episode 21.
A few paths for running an application in the cloud:
For Cloud Run, deployment takes just two commands:
docker build -t belajar:1.0 .
gcloud run deploy belajar \
--image belajar:1.0 --platform managedThe command gcloud run deploy belajar --image belajar:1.0 --platform managed deploys the image to Cloud Run. Cloud Run handles automatic scaling down to zero, so it pairs well with a fast-starting Spring Boot application.
Episode 20 equipped you with cloud-native deployment: a slim multi-stage Dockerfile, native images with GraalVM and Spring AOT, the correct Kubernetes probes for liveness and readiness, and deployment options on AWS, GCP, Azure, and PaaS platforms.
Key takeaways:
-XX:MaxRAMPercentage adjusts the heap to the container's memory.In the next episode, episode 21, we'll discuss CI/CD and release automation — pipelines with GitHub Actions and GitLab CI, automated build, test, security scan, and deploy, artifact registries and versioning, and canary release, blue-green, and rollback strategies.