Containers and Docker — Deep Dive

Level: Beginner → Intermediate
Pre-reading: 09 · Deployment & Infrastructure


Container Concepts

Concept Description
Image Immutable template containing app + dependencies
Container Running instance of an image
Registry Repository for storing images
Layer Image built in layers; shared between images

Dockerfile Best Practices

# Use specific version, not latest
FROM eclipse-temurin:17-jre-alpine

# Non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# Copy only what's needed
COPY --chown=appuser:appgroup target/app.jar /app/

# Expose port documentation
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget -q --spider http://localhost:8080/actuator/health || exit 1

# Clear entrypoint
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

Multi-Stage Builds

# Build stage
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests

# Runtime stage (smaller image)
FROM eclipse-temurin:17-jre-alpine
COPY --from=build /app/target/app.jar /app/app.jar
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Without Multi-Stage With Multi-Stage
800MB+ (includes Maven, JDK) ~150MB (JRE only)

Image Security

Practice Rationale
Minimal base image Less attack surface (alpine, distroless)
Non-root user Limit container privileges
No secrets in image Use runtime injection
Scan for vulnerabilities Trivy, Snyk
Pin versions Reproducible builds

Container vs VM

Aspect Container VM
Isolation Process-level Hardware-level
Size MBs GBs
Startup Seconds Minutes
Resource usage Shared kernel Full OS per VM

Why use multi-stage builds?

Multi-stage builds create smaller, more secure images by separating build dependencies from runtime. Build stage includes compilers, build tools. Runtime stage includes only what's needed to run. Result: faster pulls, smaller attack surface.

Why run containers as non-root?

If a container is compromised, non-root limits damage. Root in container = root on host (with certain misconfigurations). Always use non-root unless absolutely necessary.

Alpine vs Distroless — which to choose?

Alpine: Minimal Linux with shell and package manager; easy to debug. Distroless: No shell, no package manager; smallest attack surface. Use Alpine for development; consider Distroless for production security-critical apps.