Skip to content
Home / Skills / Devops / Containerization
DE

Containerization

Devops core v1.0.0

Containerization

Overview

Containerization packages applications with their dependencies into lightweight, portable images that run consistently across environments. Docker multi-stage builds produce minimal production images, while Docker Compose provides local development environments that mirror production topology. In the full-lifecycle pipeline, @devops-engineer generates Dockerfiles and compose configurations during Phase 11 based on the architecture and tech stack defined earlier.


Key Concepts

Container Image Layers

┌─────────────────────────────────────┐
│  Application JAR / Build Output     │  ← Changes often (top layer)
├─────────────────────────────────────┤
│  Application Dependencies           │  ← Changes with dependency updates
├─────────────────────────────────────┤
│  Runtime (JRE 21 / Node 20)         │  ← Changes rarely
├─────────────────────────────────────┤
│  Base OS (Alpine / Distroless)      │  ← Changes very rarely
└─────────────────────────────────────┘
    ↑ Arrange layers: least-changing at bottom

Base Image Selection

Base ImageSizeSecurityUse Case
eclipse-temurin:21-jre-alpine~80MBGoodJava production
gcr.io/distroless/java21~100MBBest (no shell)High-security Java
node:20-alpine~50MBGoodNode.js production
nginx:alpine~25MBGoodStatic frontend
ubuntu:24.04~75MBLow (full OS)Avoid for production

Best Practices

  1. Use multi-stage builds — Separate build and runtime stages; don’t ship compilers
  2. Use Alpine or Distroless — Minimal base images reduce attack surface
  3. Order layers by change frequency — Dependencies before source code
  4. Use .dockerignore — Exclude .git, node_modules, target, IDE files
  5. Run as non-rootUSER 1001:1001 for security
  6. Set health checksHEALTHCHECK CMD curl -f http://localhost:8080/actuator/health
  7. Pin base image versionsFROM node:20.11-alpine, not FROM node:latest
  8. Scan imagesdocker scout or trivy image in CI pipeline

Code Examples

✅ Good: Multi-Stage Java Dockerfile

# Stage 1: Build
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /build

# Cache dependencies (changes less often than source)
COPY pom.xml mvnw ./
COPY .mvn .mvn
RUN ./mvnw dependency:go-offline -B

# Build application
COPY src src
RUN ./mvnw package -DskipTests -B
RUN java -Djarmode=layertools -jar target/*.jar extract --destination extracted

# Stage 2: Runtime
FROM eclipse-temurin:21-jre-alpine AS runtime

# Security: run as non-root
RUN addgroup -g 1001 appgroup && adduser -u 1001 -G appgroup -D appuser

WORKDIR /app

# Copy layers (ordered by change frequency)
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/ ./

# Security and configuration
USER 1001:1001
EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1

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

✅ Good: React Frontend Dockerfile

# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts

COPY . .
RUN npm run build

# Stage 2: Serve
FROM nginx:1.25-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

USER nginx
EXPOSE 80
HEALTHCHECK CMD wget --spider -q http://localhost:80/health || exit 1

✅ Good: Docker Compose for Local Development

# docker-compose.yml
version: '3.9'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      target: builder          # Use build stage for hot-reload
    ports:
      - "8080:8080"
    environment:
      SPRING_PROFILES_ACTIVE: local
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/appdb
      SPRING_DATASOURCE_USERNAME: app
      SPRING_DATASOURCE_PASSWORD: app
      SPRING_KAFKA_BOOTSTRAP_SERVERS: kafka:9092
    depends_on:
      postgres:
        condition: service_healthy
    volumes:
      - ./src:/build/src      # Hot reload source

  postgres:
    image: postgres:16-alpine
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]

  kafka:
    image: confluentinc/cp-kafka:7.6.0
    ports:
      - "9092:9092"
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:29093
      KAFKA_LISTENERS: PLAINTEXT://kafka:9092,CONTROLLER://kafka:29093
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER

volumes:
  postgres_data:

❌ Bad: Dockerfile Anti-Patterns

FROM ubuntu:latest                     # Untagged, huge base image
RUN apt-get update && apt-get install -y openjdk-21-jdk maven  # Full JDK in runtime
COPY . /app                            # Copies everything (no .dockerignore)
WORKDIR /app
RUN mvn package                        # No dependency caching
EXPOSE 8080
CMD ["java", "-jar", "target/app.jar"] # Running as root

Anti-Patterns

  1. Single-stage builds — Ships JDK, Maven, source code in production image
  2. Running as root — Container compromise = host compromise
  3. No health checks — Orchestrator can’t detect unhealthy containers
  4. Using latest tag — Non-reproducible builds; breaks without warning
  5. No .dockerignore — Context includes .git, node_modules, bloating image
  6. Fat base images — Ubuntu/Debian when Alpine suffices

Testing Strategies

  • Image scanningtrivy image myapp:latest in CI
  • Container structure tests — Google’s container-structure-test for file/metadata assertions
  • Docker Compose integrationdocker compose up + health check + test + teardown
  • Image size tracking — Alert if image exceeds size threshold

References