โ† Back | Docker & Containers
Week 4โ€“5
Week 4โ€“5 ยท Cloud & DevOps

Docker & Containers

Docker is how modern applications are packaged and deployed. Every cloud platform, Kubernetes cluster, and CI/CD pipeline works with Docker containers. This skill is mandatory.

๐Ÿณ Dockerfile ๐Ÿ“ฆ docker-compose โ˜๏ธ Registry Push
๐Ÿ“„
Step 1
Writing a Dockerfile
Packaging your Spring Boot app
A Dockerfile is a recipe. Each instruction (FROM, COPY, RUN) adds a layer to the image. Docker caches layers โ€” if you haven't changed a layer, it uses the cache. This is why we copy pom.xml first (rarely changes), download dependencies, then copy source code (changes often).
Dockerfile โ€” Production-ready Spring Boot
# โ•โ•โ• Stage 1: Build โ•โ•โ• FROM eclipse-temurin:21-jdk-alpine AS builder WORKDIR /app # Copy Maven files first (layer cache: only re-download if pom.xml changes) COPY pom.xml . COPY .mvn .mvn COPY mvnw . RUN ./mvnw dependency:go-offline -B # Copy source and build COPY src src RUN ./mvnw clean package -DskipTests # โ•โ•โ• Stage 2: Run โ•โ•โ• FROM eclipse-temurin:21-jre-alpine WORKDIR /app # Create non-root user for security RUN addgroup -S spring && adduser -S spring -G spring USER spring:spring # Copy only the JAR from build stage COPY --from=builder /app/target/*.jar app.jar # Document which port the app uses EXPOSE 8080 # Start the app CMD ["java", "-jar", "app.jar"]
InstructionPurpose
FROMBase image to start from (always first)
WORKDIRSet working directory inside container
COPYCopy files from host โ†’ container
RUNExecute command during BUILD (creates a new layer)
EXPOSEDocument port (doesn't actually publish)
CMDDefault command to run when container starts
ENVSet environment variable
ARGBuild-time variable
๐Ÿš€
Step 2
Build and Run Your Image
# Build the image (-t = tag/name) docker build -t student-api:latest . docker build -t student-api:1.0.0 . # List images docker images # Run the container docker run \ -d \ # detached (background) -p 8080:8080 \ # host:container port mapping --name student-api \ # container name -e SPRING_PROFILES_ACTIVE=dev \ # set environment variable student-api:latest # Test it curl http://localhost:8080/api/students # View logs docker logs student-api docker logs -f student-api # follow (live tail) # Open shell inside running container docker exec -it student-api sh # Stop and remove docker stop student-api docker rm student-api
๐Ÿ’พ
Step 3
Volumes and Networking

Volumes โ€” Persistent Data

# Named volume โ€” persists beyond container lifecycle docker volume create postgres-data docker run \ -d \ -p 5432:5432 \ --name postgres-db \ -e POSTGRES_PASSWORD=secret \ -e POSTGRES_DB=studentdb \ -v postgres-data:/var/lib/postgresql/data \ postgres:16-alpine # Bind mount โ€” map a host folder to container (useful in dev) docker run -v /host/path:/container/path myapp

Networking โ€” Containers Talking to Each Other

# Create a custom network docker network create student-net # Run containers on the same network docker run -d --name postgres-db --network student-net postgres:16-alpine docker run -d --name student-api --network student-net \ -e SPRING_DATASOURCE_URL=jdbc:postgresql://postgres-db:5432/studentdb \ -p 8080:8080 \ student-api:latest # Containers on the same network can reach each other by NAME # student-api can connect to postgres using hostname "postgres-db"
๐ŸŽผ
Step 4
docker-compose
Define and run multi-container apps in one file
docker-compose.yml
version: '3.9' services: # PostgreSQL database db: image: postgres:16-alpine container_name: student-db environment: POSTGRES_DB: studentdb POSTGRES_USER: postgres POSTGRES_PASSWORD: secret volumes: - postgres-data:/var/lib/postgresql/data ports: - "5432:5432" # Spring Boot API api: build: . # Build from Dockerfile in current dir container_name: student-api ports: - "8080:8080" environment: SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/studentdb SPRING_DATASOURCE_USERNAME: postgres SPRING_DATASOURCE_PASSWORD: secret SPRING_JPA_HIBERNATE_DDL_AUTO: update depends_on: - db # Start db before api volumes: postgres-data:
# Start everything docker compose up -d # View running services docker compose ps # View logs for all services docker compose logs -f # Rebuild after code change docker compose up -d --build # Stop and remove everything docker compose down docker compose down -v # also remove volumes (wipes DB data)
This is the standard dev setup. One command starts your entire stack โ€” API + database. No more "I need to install PostgreSQL" โ€” the container handles everything.
๐ŸŒฑ
Best Practice
Spring Boot Docker Optimization
# application-docker.properties spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:studentdb} spring.datasource.username=${DB_USER:postgres} spring.datasource.password=${DB_PASSWORD:secret} # Use Docker profile when running in container # Set env: SPRING_PROFILES_ACTIVE=docker
.dockerignore โ€” keep image small
.git .gitignore target/ *.md .mvn/wrapper/maven-wrapper.jar
Image size matters. Use alpine base images (e.g., eclipse-temurin:21-jre-alpine instead of eclipse-temurin:21-jre). Alpine is ~5MB vs ~200MB for the full image.
๐ŸŽฏ
Interview Prep
Common Interview Questions
QWhat is the difference between COPY and ADD in a Dockerfile?

COPY simply copies files or directories from host to container. It's straightforward and predictable โ€” recommended for most use cases.

ADD does everything COPY does plus: automatically extracts tar archives, and can download from URLs. Because of its extra magic, it can cause surprises. Best practice: use COPY unless you specifically need ADD's extra features.

QWhat is a multi-stage Dockerfile and why use it?

Multi-stage builds use multiple FROM statements. Each stage starts fresh from a base image. You can copy artifacts from one stage to another.

Why: the build stage needs JDK + Maven + source code (large). The final stage only needs JRE + the JAR (small). Without multi-stage, your production image would contain the entire JDK and source code. With multi-stage, the final image is 5-10x smaller and doesn't expose source code.

QWhat is the difference between EXPOSE and -p in Docker?

EXPOSE in Dockerfile is documentation โ€” it tells other developers which port the container listens on. It does NOT actually publish the port to the host.

-p 8080:8080 in docker run actually publishes the port โ€” maps port 8080 on the host to port 8080 in the container, making it accessible from outside Docker.

QHow do containers in a docker-compose communicate with each other?

Containers defined in the same docker-compose.yml are automatically on the same Docker network. They can reach each other using the service name as the hostname.

So if your compose file has a service named db, the Spring Boot app can connect to it at jdbc:postgresql://db:5432/studentdb โ€” the hostname is just db, not localhost or an IP address.