Home/Learn/Microservices/Containerisation with Docker

Containerisation with Docker

Intermediate
Deployment

Each microservice is packaged as a Docker image; multi-stage builds keep images small, and images are tagged with the commit SHA for traceability.

Overview

Containerisation with Docker packages a microservice and all its dependencies (JRE, config, static assets) into a single portable image that runs identically in dev, staging, and production. Each microservice produces its own Docker image with a semantic version or commit SHA tag. The image is stored in a container registry (ECR, GCR, Docker Hub) and pulled by Kubernetes when deploying. For Spring Boot, the key best practices are: multi-stage builds to keep images small, layered JARs to maximise Docker layer cache reuse (fast rebuilds), running as a non-root user, and using a minimal base image. Spring Boot 2.3+ can also produce OCI images directly with the Buildpacks plugin — no Dockerfile needed.

Dockerfile for Spring Boot — Multi-Stage Build

A multi-stage Dockerfile separates the build environment from the runtime environment. The final image contains only the JRE and the application — no Maven/Gradle, no source code. This keeps the image small and eliminates build tools from the attack surface.

Dockerfile — Multi-Stage Spring Boot Build
# Stage 1: Build — uses full JDK + Maven, not included in final image
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /build

# Cache dependency layer separately (only invalidated when pom.xml changes)
COPY pom.xml .
COPY .mvn .mvn
COPY mvnw .
RUN ./mvnw dependency:go-offline -q

# Copy source and build
COPY src ./src
RUN ./mvnw package -DskipTests -q

# Extract layered JAR (Spring Boot 2.3+ layered JAR support)
RUN java -Djarmode=layertools -jar target/*.jar extract --destination extracted

# Stage 2: Runtime — minimal JRE image, no build tools
FROM eclipse-temurin:21-jre AS runtime

WORKDIR /app
RUN addgroup --system app && adduser --system --group app
USER app   # run as non-root

# Copy layers in order of change frequency (stable layers first for cache)
COPY --from=builder /build/extracted/dependencies/ ./
COPY --from=builder /build/extracted/spring-boot-loader/ ./
COPY --from=builder /build/extracted/snapshot-dependencies/ ./
COPY --from=builder /build/extracted/application/ ./

EXPOSE 8080
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]

Image Tagging and Registry

Images should be tagged with immutable identifiers for traceability and rollback:

- **Commit SHA tag**: `myregistry/order-service:a3f8c1d` — points to exact code, never changes. - **Semantic version**: `myregistry/order-service:1.4.2` — human-readable, used alongside SHA. - **Avoid `latest` in production** — mutable tag; different builds can produce the same `latest`, making rollback impossible.

Kubernetes Deployment manifests should always pin to a specific tag; CI/CD pipelines update the tag on each deployment.

Shell + YAML — Image Tagging
# Build and tag with commit SHA + semantic version
export VERSION=1.4.2
export COMMIT_SHA=$(git rev-parse --short HEAD)
export IMAGE=123456789.dkr.ecr.us-east-1.amazonaws.com/order-service

docker build -t ${IMAGE}:${VERSION} -t ${IMAGE}:${COMMIT_SHA} .
docker push ${IMAGE}:${VERSION}
docker push ${IMAGE}:${COMMIT_SHA}

# Kubernetes Deployment — always pin to specific tag
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: order-service
        image: 123456789.dkr.ecr.us-east-1.amazonaws.com/order-service:1.4.2
        imagePullPolicy: IfNotPresent  # don't re-pull if already on node

# Spring Boot Buildpacks (no Dockerfile needed)
# Produces an OCI image via Cloud Native Buildpacks
./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=${IMAGE}:${VERSION}

Container Best Practices for Production

Key production hardening practices for Spring Boot containers:

1. **Non-root user** — never run as root; reduces blast radius if container is compromised. 2. **Read-only filesystem** — mount /tmp as tmpfs; prevents writes to container FS. 3. **Resource limits** — always set CPU/memory requests and limits in K8s; prevents noisy-neighbour starvation. 4. **Health probe endpoints** — /actuator/health/liveness and /readiness. 5. **Graceful shutdown** — server.shutdown=graceful to drain in-flight requests. 6. **JVM memory tuning** — set -XX:MaxRAMPercentage=75.0 to size heap relative to container memory limit.

YAML — K8s Container Security
# Kubernetes Deployment — production hardening
spec:
  containers:
  - name: order-service
    image: myregistry/order-service:1.4.2
    resources:
      requests:
        memory: "512Mi"
        cpu: "250m"
      limits:
        memory: "1Gi"
        cpu: "500m"
    env:
    - name: JAVA_TOOL_OPTIONS
      value: "-XX:MaxRAMPercentage=75.0 -XX:+UseContainerSupport -Xss512k"
    securityContext:
      runAsNonRoot: true
      runAsUser: 1000
      readOnlyRootFilesystem: true
      allowPrivilegeEscalation: false
    volumeMounts:
    - name: tmp-dir
      mountPath: /tmp
  volumes:
  - name: tmp-dir
    emptyDir: {}

Key Points to Remember

  • 1Multi-stage Dockerfile: build in JDK stage, copy only the JAR to a minimal JRE runtime stage — keeps images small and secure.
  • 2Layered JARs (Spring Boot 2.3+) split the fat JAR into layers ordered by change frequency, maximising Docker layer cache reuse.
  • 3Tag images with commit SHA and semantic version; never use `latest` in production — it prevents reliable rollbacks.
  • 4Always run containers as non-root users — set runAsNonRoot: true and a specific UID in the K8s securityContext.
  • 5Set JVM flag -XX:MaxRAMPercentage=75.0 so the JVM respects container memory limits instead of using the host RAM.
  • 6Always set K8s resource requests and limits — requests are used for scheduling; limits cap CPU/memory consumption.

Interview Questions

Sign in to ask Aria
1

What is a multi-stage Docker build and why is it useful for Spring Boot services?

EasyAmazon
2

Why should you avoid using the `latest` tag in a Kubernetes Deployment?

EasyGoogle
3

What is a layered JAR in Spring Boot and how does it improve Docker build performance?

MediumNetflix
4

What JVM flag should you set to make the JVM respect container memory limits?

MediumUber
5

What security settings would you apply to a Spring Boot container running in Kubernetes?

HardLinkedIn

Ask Aria about Containerisation with Docker

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…