Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Dockerizing Spring Boot
Shipping JARs is so 2010. We ship Images.1. The Easy Way: Cloud Native Buildpacks
Spring Boot has built-in Docker support. No Dockerfile needed!demo:0.0.1-SNAPSHOT using optimized layed JARs.
2. The Manual Way: Dockerfile
If you need custom control.docker build -t my-app .
3. Docker Compose for Development
Spin up your whole stack: Postgres, RabbitMQ, Zipkin, and your Apps.docker-compose.yml:
docker-compose up -d
4. Jib (Google’s Plugin)
Builds optimized Docker images without a Docker daemon! Great for CI/CD pipelines. Add plugin topom.xml:
./mvnw jib:dockerBuild
Interview Deep-Dive
Why does the Dockerfile example use a multi-stage build with separate build and run stages? What are the security and performance implications of not doing this?
Why does the Dockerfile example use a multi-stage build with separate build and run stages? What are the security and performance implications of not doing this?
Strong Answer:
- A multi-stage build separates the build environment (JDK, Maven, source code, build tools) from the runtime environment (JRE, compiled JAR). The final image contains ONLY what is needed to run the application.
- Without multi-stage: the image includes the full JDK (300MB+ vs. JRE’s 150MB), your entire source code (potentially including credentials if
.gitignoreis misconfigured), Maven’s local repository cache (hundreds of MB of downloaded dependencies), and build tools. This bloats the image from ~200MB to 800MB+. - Security implication: the JDK includes
javac,jdb,jconsole, and other diagnostic tools. If an attacker gains shell access to the container, these tools make exploitation significantly easier. The JRE-only runtime reduces the attack surface. Even better, use a distroless base image (gcr.io/distroless/java17-debian12) — no shell, no package manager, no utilities. An attacker who gains code execution cannot runls,curl, orbash. - Performance implication: smaller images mean faster pulls. In Kubernetes, when a pod is scheduled on a new node, it must download the image. An 800MB image takes 30+ seconds on a typical network; a 200MB image takes 8 seconds. This directly impacts your deployment speed and auto-scaling responsiveness.
- Layer caching: the Dockerfile is ordered deliberately. Dependencies (pom.xml) are copied and resolved BEFORE source code. Since dependencies change less frequently than source code, Docker caches the dependency layer. On subsequent builds, only the source code layer is rebuilt, reducing build time from minutes to seconds.
./mvnw spring-boot:build-image) automatically create an optimized layered image without a Dockerfile. They use Paketo buildpacks that detect your app type, select the appropriate JRE, configure JVM memory settings based on container limits, and create a layered JAR (dependencies, Spring libraries, snapshot dependencies, application code in separate layers). Advantage: zero Dockerfile maintenance, best-practice defaults, automatic security patching when you rebuild (new JRE patches are pulled). Disadvantage: less control over the base image, slower build process, and opaque — harder to debug when something goes wrong. I prefer Buildpacks for standard Spring Boot services where you do not need custom OS packages. I prefer Dockerfiles when you need custom native libraries, specific base images mandated by security policy, or multi-service builds in a single Dockerfile (monorepo pattern).How do you properly configure JVM memory settings when running a Spring Boot application inside a Docker container?
How do you properly configure JVM memory settings when running a Spring Boot application inside a Docker container?
Strong Answer:
- The critical problem: older JVMs (pre-Java 10) do not respect container memory limits. A container with 512MB RAM runs on a host with 64GB. The JVM reads the HOST memory (64GB) and sets its default heap to 16GB (
-Xmx= 1/4 of physical memory). The container is killed by the OOM killer immediately. - Modern JVMs (Java 10+): the JVM is container-aware by default (
-XX:+UseContainerSupport, on by default). It reads the container’s cgroup memory limit and sizes the heap accordingly. With 512MB container memory, the JVM sets the max heap to ~256MB (a conservative percentage of container memory, leaving room for metaspace, thread stacks, native memory, and the container overhead). - Fine-tuning: use
-XX:MaxRAMPercentage=75.0instead of a fixed-Xmx. This tells the JVM to use 75% of the container’s memory for the heap, adapting to different container sizes without hardcoded values. The remaining 25% covers metaspace (~100MB for Spring Boot), thread stacks (1MB per thread, 200 threads = 200MB), native memory (NIO buffers, JNI), and OS overhead. - Common production mistake: setting
-Xmx=512mon a 512MB container. The heap is 512MB, but the JVM also needs 150MB for metaspace, 200MB for threads, and 50MB for internal bookkeeping. Total: ~900MB. Container is killed. Always set the heap to 60-75% of the container limit. - Spring Boot Buildpacks handle this automatically: the Paketo Java buildpack includes a memory calculator that reads the container memory limit and computes optimal
-Xmx,-Xms, metaspace, and thread stack values.
jvm.memory.used{area=nonheap}. (2) Thread stacks — each thread consumes 1MB by default (-Xss). A Tomcat app with 200 request threads + 50 async threads = 250MB just for stacks. (3) Direct ByteBuffers — used by NIO, Netty, and memory-mapped files. If you use WebClient (Netty) heavily, this can grow significantly. (4) JNI and native libraries (compression, TLS). Monitor total RSS with docker stats and compare against JVM metrics. The gap is your native memory. Reduce with: -XX:MaxMetaspaceSize=150m, -Xss512k (reduce thread stack if your code is not deeply recursive), and -XX:MaxDirectMemorySize for NIO buffer limits.Your team uses Docker Compose for local development with 6 services, PostgreSQL, Redis, Kafka, and Zipkin. Startup takes 3 minutes and developers complain. How do you optimize the development experience?
Your team uses Docker Compose for local development with 6 services, PostgreSQL, Redis, Kafka, and Zipkin. Startup takes 3 minutes and developers complain. How do you optimize the development experience?
Strong Answer:
- Problem diagnosis: 3 minutes likely means services are starting before their dependencies are ready (Kafka is not up when OrderService starts, causing connection failures and restart loops), Docker is rebuilding images from scratch each time, and services are competing for CPU during the startup burst.
- Fix 1 — Dependency health checks: Replace
depends_on(which only waits for container start, not readiness) with health-check-based dependencies.depends_on: postgres: condition: service_healthywith a health check liketest: ["CMD-SHELL", "pg_isready -U user"]. This ensures PostgreSQL is actually ready to accept connections before dependent services start. - Fix 2 — Volume mount for code: Instead of rebuilding the Docker image for every code change, mount the source code as a volume and use Spring DevTools with
spring-boot-devtools. Code changes trigger an automatic restart inside the container without rebuilding the image. Build time drops from 60 seconds to 2 seconds for live reload. - Fix 3 — Pre-built infrastructure: Keep infrastructure containers (PostgreSQL, Redis, Kafka, Zipkin) running permanently. Only restart application containers during development. Use
docker-compose up -d postgres redis kafka zipkinonce, and run your Spring Boot services locally (outside Docker) pointed atlocalhost:5432,localhost:6379, etc. This eliminates container overhead for the services you are actively developing. - Fix 4 — Docker layer caching: Structure Dockerfiles to copy
pom.xmland resolve dependencies before copying source code. Dependencies are cached; only the source code layer rebuilds on changes. - Fix 5 — Docker Compose profiles: Define profiles for different workflows.
docker compose --profile full upstarts everything.docker compose --profile infra upstarts only infrastructure. Most development only needs infra + the one service you are working on.
postgres), Kubernetes uses service DNS (postgres.namespace.svc.cluster.local). Use Spring config profiles to abstract this. (2) Resource limits — Docker Compose typically runs without memory/CPU limits; production has strict limits. Add resource constraints to Compose: deploy: resources: limits: memory: 512M. (3) Networking — Compose puts everything on one network; Kubernetes has NetworkPolicies. A service-to-service call that works in Compose might be blocked by a NetworkPolicy in production. (4) Secrets — Compose uses environment variables; production uses Kubernetes Secrets or Vault. Use Spring’s @ConfigurationProperties to abstract the source. The best investment: run integration tests in a CI environment that mirrors production as closely as possible (Kubernetes-in-Docker, or a staging namespace).