Dockerizing, distroless concepts
On this page
Why Naive Dockerfiles Fail in Production
A Docker image that works locally is not automatically production-ready. Production symptoms of poor containerization: - Image size > 1GB - Slow startup times - High memory usage - Running as root - CVE scan full of critical vulnerabilities - OOMKilled in Kubernetes Containers are runtime environments. They must be designed, not improvised.Incident Scenario: Service OOMKilled After Migration to Kubernetes
A service ran fine on a VM. After moving to Kubernetes with 512Mi memory limit, pods started crashing. Root cause: JVM ignored container memory limits. Default heap sizing assumed full node memory. GC pressure triggered OOMKill. The Dockerfile was correct syntactically. It was wrong operationally.Anti-Pattern: Single-Stage Fat Dockerfile
FROM openjdk:latest COPY build/libs/app.jar app.jar CMD ["java", "-jar", "app.jar"]Problems: - latest tag is non-deterministic - full JDK included unnecessarily - large attack surface - no user isolation - no JVM container tuning This is demo-level, not production-grade.
Correct Pattern: Multi-Stage Build
Separate build from runtime.FROM gradle:8.5-jdk21 AS builder WORKDIR /app COPY . . RUN gradle build --no-daemon FROM eclipse-temurin:21-jre WORKDIR /app COPY --from=builder /app/build/libs/app.jar app.jar RUN addgroup --system app && adduser --system --ingroup app app USER app ENTRYPOINT ["java","-XX:+UseContainerSupport","-jar","app.jar"]Benefits: - No build tools in final image - Smaller runtime footprint - Reduced CVE surface - Non-root execution
Distroless Concept
Distroless images remove: - shell - package manager - unnecessary binaries Advantages: - Smaller attack surface - Fewer vulnerabilities - Reduced image size Tradeoff: - Harder debugging (no shell inside container) Distroless is excellent for stable production workloads. Use debug-friendly base image for development, minimal for prod.Run As Non-Root
Running as root inside container is still risky: - breakout impact increases - file permission escalation risk Always: - create non-root user - switch USER in Dockerfile - ensure filesystem permissions allow execution This is a minimal but important defense.Layering and Caching
Optimize build cache: - copy build files first - resolve dependencies - then copy source This reduces CI build time significantly. Also: - leverage Spring Boot layered jar if applicable - separate dependencies layer from application layer Faster builds = faster feedback = safer deploys.JVM Container Awareness
Modern JVM supports container memory awareness, but do not assume defaults are perfect. Tune: - -XX:MaxRAMPercentage - -XX:InitialRAMPercentage - GC selection appropriate for memory limit Example:ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "-XX:InitialRAMPercentage=50.0", "-XX:+UseContainerSupport", "-jar","app.jar"]Without tuning: - heap may be too large - GC pauses may increase - container limit may be exceeded