Stage 12: Microservices, lesson 6 of 7

Docker, Kubernetes and observability

Intermediate3 min readall versions
Explain it forThe essentials plus production detail and pitfalls.

Docker packages the app and its runtime into an image. Spring Boot can build one without a Dockerfile: ./mvnw spring-boot:build-image (Cloud Native Buildpacks).

Kubernetes runs containers in Pods, managed by Deployments (replicas and rolling updates), exposed by Services, configured with ConfigMaps and Secrets, and scaled by the HorizontalPodAutoscaler.

Observability has three pillars:

  • Logs: structured JSON with trace ids. Spring Boot 3.4+ supports structured logging natively.
  • Metrics: Micrometer, scraped by Prometheus, shown in Grafana.
  • Traces: Micrometer Tracing or OpenTelemetry, sent to Zipkin, Jaeger or Tempo, to follow one request across services.

Actuator exposes /actuator/health, /actuator/metrics and /actuator/prometheus.

Example

Dockerfile
# Dockerfile: multi-stage, small, non-root
FROM eclipse-temurin:25-jdk AS build
WORKDIR /app
COPY . .
RUN ./mvnw -q package -DskipTests

FROM eclipse-temurin:25-jre
WORKDIR /app
RUN useradd -r app
USER app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "app.jar"]
deployment.yaml (excerpt)
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: catalog
          image: ghcr.io/javaatlas/catalog:1.4.0
          resources:
            requests: { cpu: "250m", memory: "512Mi" }
            limits:   { memory: "768Mi" }
          readinessProbe:
            httpGet: { path: /actuator/health/readiness, port: 8080 }
          livenessProbe:
            httpGet: { path: /actuator/health/liveness, port: 8080 }

Common mistake

Building a large image with the full JDK and running it as root. Use a JRE base image, a multi-stage build, a non-root user, and pinned image versions.

Under the hood

Liveness answers "should I be restarted?"; readiness answers "can I take traffic?". Don't check the database in the liveness probe, or one database blip restarts every pod. Graceful shutdown, on by default since Spring Boot 3.4, lets in-flight requests finish during rolling updates. For fast startup and low memory, consider GraalVM native images or the ahead-of-time caches in Java 24+ (Project Leyden).

Check yourself

Which probe failure causes a container restart?

How this connects

Part of Job-ready backend developer, Microservices and production.

Was this lesson helpful?

Finished reading? Mark it complete to track your progress.