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.