Spring Boot Integration: Dockerizing, PostgreSQL, Redis, Kafka & Microservices
This chapter applies everything from Chapters 1-8 to the stack most readers actually run day to day: a Spring Boot service backed by PostgreSQL, Redis, and Kafka, deployed as containers and composed t
This chapter applies everything from Chapters 1-8 to the stack most readers actually run day to day: a Spring Boot service backed by PostgreSQL, Redis, and Kafka, deployed as containers and composed together. It is the synthesis chapter most likely to map directly onto real interview system-design questions.
Spring Boot has first-class support for containerization via Cloud Native Buildpacks integration (mvnw spring-boot:build-image) as an alternative to hand-written Dockerfiles, plus excellent layered-jar support that maps naturally onto Docker's layer caching model.
Connecting to PostgreSQL, Redis, and Kafka from a containerized Spring Boot app is fundamentally a service-discovery problem (Chapter 5/6): connection strings use service names, not IPs, and startup-order/readiness must be handled deliberately since Spring's default behavior is to fail fast on an unavailable dependency rather than wait indefinitely.
-
Spring Boot's layered jar feature (layertools) splits the fat jar into separate layers — dependencies (rarely change), spring-boot-loader, snapshot-dependencies, and application classes (change every build) — letting a Dockerfile COPY each layer separately so only the actual application-code layer is rebuilt on most code changes, dramatically improving cache hit rates versus copying one monolithic fat jar.
-
mvnw spring-boot:build-image uses Cloud Native Buildpacks (via Paketo) to produce an OCI-compliant image without writing a Dockerfile at all — it automatically selects a JRE, applies layering, and can target distroless-style minimal runtime images, trading some control for convenience and buildpack-maintained security patching.
-
Spring's spring.datasource.url=jdbc:postgresql://db:5432/appdb resolves db via the container's embedded DNS (Chapter 5/6) at connection time — if the database container isn't ready yet, the connection pool (HikariCP) will retry per its configured timeout/retry settings, but a fully cold start can still fail without orchestration-level readiness gating (Compose healthcheck conditions, or Kubernetes init containers).
-
Redis integration via Spring Data Redis or Spring Cache abstraction connects similarly by hostname; container restarts of Redis (an in-memory store, by default non-persistent unless configured with AOF/RDB persistence and a volume) mean cached data can vanish on Redis container restart unless persistence is explicitly configured and volume-backed.
-
Kafka integration (spring-kafka) requires the application to know the broker's advertised listener address — a classic Kafka-in-Docker pitfall is a broker advertising localhost:9092 to clients outside its own container network, making it unreachable from sibling containers even though docker logs shows it 'running fine'.
-
In a microservices Compose/Kubernetes topology, each service is its own image/container with its own dependencies, and inter-service calls (REST, messaging) resolve through the same DNS-based service discovery mechanism, whether that's Compose's embedded DNS or Kubernetes Services/CoreDNS.
-
Enable layered jar packaging in pom.xml (default in modern Spring Boot via spring-boot-maven-plugin layers configuration) and build with mvn package.
-
In the Dockerfile's builder stage, run java -Djarmode=layertools -jar app.jar extract to split the jar into layer directories.
-
In the final stage, COPY each extracted layer separately, ordered from least to most frequently changed, so Docker's cache reuses the dependency layers on every build that only touches application code.
-
Configure spring.datasource.url, spring.redis.host, and spring.kafka.bootstrap-servers using Compose service names (db, redis, kafka), never hardcoded IPs.
-
Add a Compose healthcheck to Postgres/Kafka and gate the Spring Boot service's depends_on on condition: service_healthy so the app doesn't attempt connections before its dependencies are genuinely ready.
-
Configure Kafka's KAFKA_ADVERTISED_LISTENERS to advertise the correct internal Docker network hostname (not localhost) so other containers can actually establish a connection after the initial bootstrap handshake.
-
Expose /actuator/health (with readiness/liveness probe groups enabled) so both Compose and Kubernetes can make accurate startup and ongoing health decisions about the service.
-
A typical backend stack: Spring Boot API plus Postgres (relational data), Redis (session/cache), and Kafka (async event processing between services), all defined in one Compose file for local development that mirrors production topology.
-
Microservices decomposition: separate order-service and inventory-service containers communicating via Kafka events rather than direct synchronous calls, demonstrating eventual consistency patterns in an interview system-design discussion.
-
Using Spring Boot Actuator's readiness/liveness probe groups to integrate cleanly with Kubernetes probes (Chapter 11) when the same containerized application graduates from Compose to a cluster.
-
Local integration testing using Testcontainers (a Java library that programmatically starts real Postgres/Kafka/Redis containers for test runs), which builds directly on the Docker concepts covered so far.
-
Use Spring Boot's layered jar feature in every production Dockerfile — it's a small change with an outsized cache-efficiency benefit for Java applications specifically.
-
Always configure connection pool timeouts (HikariCP connection-timeout) explicitly rather than relying on defaults, since container-networked dependencies can have brief unavailability windows during rolling restarts that a sensible timeout/retry handles gracefully.
-
Expose Actuator's readiness and liveness probe groups separately — readiness should reflect 'can serve traffic right now' (DB connection pool healthy) while liveness should reflect 'is the process fundamentally stuck or deadlocked', since conflating them causes incorrect restart behavior under load.
-
For Kafka in any containerized environment, always explicitly set KAFKA_ADVERTISED_LISTENERS to the broker's actual reachable network alias — never leave it defaulting to localhost.
-
Copying a single fat jar in the Dockerfile (COPY target/app.jar app.jar) instead of using layered extraction, silently giving up most of Docker's caching benefit for Java builds.
-
Hardcoding localhost for Postgres/Redis/Kafka connection strings, which works when running the app directly on a developer's machine but fails immediately once the app itself is containerized and needs to reach sibling containers by service name instead.
-
Letting Kafka advertise localhost as its listener address — the broker appears healthy in its own logs, but every other container's producer/consumer fails to connect after the initial metadata fetch.
-
Relying on Spring Boot's default fail-fast behavior against a not-yet-ready database without Compose/Kubernetes-level readiness gating or HikariCP retry tuning, causing flaky crash-loops on cold starts.
-
Treating Redis as durable storage without enabling persistence (RDB/AOF) and a backing volume, then being surprised cached/session data disappears on every Redis container restart.
-
Layered jars reduce CI build time by reusing dependency layers across builds — measure this concretely with docker history before/after adopting layertools.
-
Tune HikariCP pool size relative to the container's actual CPU/connection limits and the database's max_connections setting — an oversized pool across many container replicas can exhaust the database's connection limit faster than expected.
-
For Kafka consumers, tune max.poll.records and consumer group partition counts to match the number of running service replicas, avoiding both under-parallelization and excessive per-partition overhead.
-
In production, externalize all environment-specific connection details (hosts, credentials) via orchestrator-native configuration (Kubernetes ConfigMaps/Secrets, or a config server) rather than baking them into images or relying solely on Compose .env files.
-
Run Kafka and Postgres as managed services (RDS, MSK, Confluent Cloud, or equivalent) in production rather than self-hosted containers when operational overhead (backups, patching, HA) outweighs the cost savings of self-hosting — Compose-based local stacks should mirror the production topology conceptually, not necessarily byte-for-byte.
-
Apply the same health-gated startup discipline in Kubernetes via readiness probes and (if genuinely needed) init containers, mirroring the Compose condition: service_healthy pattern at the cluster level.
-
Convert an existing single-COPY Dockerfile for a Spring Boot app to use layered jar extraction, and compare rebuild speed after a one-line source change.
-
Stand up the full microservices Compose stack from this chapter (gateway + 2 services + Postgres + Redis + Kafka) and verify Kafka event flow between the two services end-to-end.
-
Deliberately misconfigure KAFKA_ADVERTISED_LISTENERS to localhost, observe the connectivity failure from a sibling container, then fix it.
-
Add Actuator readiness and liveness probe groups to a Spring Boot app and verify their distinct behavior via curl during a simulated database outage.
-
Use Spring Boot layered jars in Dockerfiles for dramatically better build cache efficiency on Java applications.
-
All Postgres/Redis/Kafka connection strings inside containers must use service names, never localhost or hardcoded IPs.
-
Kafka's advertised listener configuration is the single most common 'works in logs, fails for clients' Docker pitfall.
-
Separate readiness (can serve traffic now) from liveness (process fundamentally healthy) in Actuator probe configuration.
-
Health-gate dependent services on real readiness (Compose condition: service_healthy / Kubernetes readiness probes), not just container start order.
Want a visual for this concept?
Generate a diagram tailored to “Spring Boot Integration: Dockerizing, PostgreSQL, Redis, Kafka & Microservices” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.
Sign in to generate a visual →