Ravindra BagaleCourses & study guides मराठी

Chapter 73: DevOps Projects Part 3: Docker Websites, APIs, Compose, Registry and ECR

73.4 Project 20: Dockerize a Spring Boot app with a multi-stage build

Friends, Java images get huge if you ship the JDK. Today we use a multi-stage Dockerfile: build with Maven JDK, run with a slim JRE Alpine, and prove health on 8080.

What you will build in this project

  1. A tiny Spring Boot app with /actuator/health.
  2. Dockerfile FROM eclipse-temurin:21-jre-alpine.
  3. Image spring-api:1.0.
  4. Container on host port 8080.

1. Start from a real company story

At PhonePe in Bengaluru, the Spring Boot service was copied by hand onto several VMs. One box had the wrong runtime version.

Errors during peak cost about ₹22 lakh in failed UPI retries.

Dev packed the app into eclipse-temurin:21-jre-alpine. The same tag ran on every box. Rollback was one previous tag.

Today we dockerize that Spring Boot app.

App filesSpring BootDockerfileeclipse-temurin:21-jre-alpinedocker buildspring-api:1.0run -p 8080curl proves
Picture 1: from zip copy to Spring Boot container

Ravindra Bagale's Tip

Many students copy the whole Maven cache into the final image. Use a second FROM so only the jar remains. Keep that habit.

2. Start and prepare

Step 1: Make project folder

Why: We need a clean folder for the Spring Boot app. We do this before the next step so mistakes stay small and easy to undo. Read the output; the names and numbers tell you if the machine is healthy.

What: mkdir and cd. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 1: mkdir

Type this:

mkdir -p /tmp/spring-api/src/main/java/com/demo /tmp/spring-api/src/main/resources && echo ready

You will see:

ready

What it means: Tree.

Example 2: pom

Type this:

printf '%s\n' '<project><modelVersion>4.0.0</modelVersion><groupId>com.demo</groupId><artifactId>spring-api</artifactId><version>1.0.0</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.4</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency></dependencies></project>' > /tmp/spring-api/pom.xml && wc -c /tmp/spring-api/pom.xml

You will see:

... bytes

What it means: pom ready (lab may use prebuilt jar).

Example 3: note

Type this:

echo 'Lab tip: if Maven is slow, use a prebuilt jar named app.jar in /tmp/spring-api'

You will see:

Lab tip: if Maven is slow, use a prebuilt jar named app.jar in /tmp/spring-api

What it means: Practical tip.

Step 2: Write app files

Why: The Spring Boot files are the product we ship. We do this before the next step so mistakes stay small and easy to undo. Read the output; the names and numbers tell you if the machine is healthy.

What: printf the source files. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 4: appjava

Type this:

printf '%s\n' 'package com.demo;' 'import org.springframework.boot.*;' 'import org.springframework.web.bind.annotation.*;' '@SpringBootApplication' '@RestController' 'public class App {' ' public static void main(String[] a){SpringApplication.run(App.class,a);}' ' @GetMapping("/api/hello") String hello(){return "{\"msg\":\"pay-api\"}";}' '}' > /tmp/spring-api/src/main/java/com/demo/App.java && wc -l /tmp/spring-api/src/main/java/com/demo/App.java

You will see:

10 ...

What it means: Controller.

Example 5: props

Type this:

printf '%s\n' 'management.endpoints.web.exposure.include=health' > /tmp/spring-api/src/main/resources/application.properties && cat /tmp/spring-api/src/main/resources/application.properties

You will see:

management.endpoints.web.exposure.include=health

What it means: Actuator health.

Example 6: tree

Type this:

find /tmp/spring-api -type f | wc -l

You will see:

3

What it means: Files.

Step 3: Local syntax check

Why: Broken syntax must fail before Docker. We do this before the next step so mistakes stay small and easy to undo. Read the output; the names and numbers tell you if the machine is healthy.

What: Language check command. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 7: mvn note

Type this:

echo 'mvn -q -DskipTests package  # produces target/*.jar'

You will see:

mvn -q -DskipTests package  # produces target/*.jar

What it means: Build step.

Example 8: jar note

Type this:

echo 'Or copy a prebuilt app.jar for the lab'

You will see:

Or copy a prebuilt app.jar for the lab

What it means: Fallback.

Example 9: ready

Type this:

ls /tmp/spring-api | head

You will see:

pom.xml
src

What it means: Ready for Docker.

3. Build the main pieces

Step 4: Write Dockerfile

Why: Dockerfile pins eclipse-temurin:21-jre-alpine. We do this before the next step so mistakes stay small and easy to undo. Read the output; the names and numbers tell you if the machine is healthy.

What: FROM pin + COPY + CMD. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 10: df

Type this:

printf '%s\n' 'FROM eclipse-temurin:21-jdk-alpine AS build' 'WORKDIR /src' 'COPY pom.xml .' 'COPY src ./src' 'RUN apk add --no-cache maven && mvn -q -DskipTests package && cp target/*.jar /app.jar' 'FROM eclipse-temurin:21-jre-alpine' 'WORKDIR /app' 'COPY --from=build /app.jar app.jar' 'EXPOSE 8080' 'USER nobody' 'CMD ["java","-jar","app.jar"]' > /tmp/spring-api/Dockerfile && head -3 /tmp/spring-api/Dockerfile

You will see:

FROM eclipse-temurin:21-jdk-alpine AS build
WORKDIR /src
COPY pom.xml .

What it means: Dockerfile written.

Example 11: from

Type this:

head -1 /tmp/spring-api/Dockerfile

You will see:

FROM eclipse-temurin:21-jdk-alpine AS build

What it means: Pinned base.

Example 12: ls

Type this:

ls /tmp/spring-api/Dockerfile

You will see:

/tmp/spring-api/Dockerfile

What it means: Present.

Step 5: Write dockerignore

Why: Context must stay lean. We do this before the next step so mistakes stay small and easy to undo. Read the output; the names and numbers tell you if the machine is healthy.

What: .dockerignore lists junk. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 13: ignore

Type this:

printf '%s\n' '.git' '*.md' '__pycache__' 'node_modules' 'target' 'bin' 'obj' > /tmp/spring-api/.dockerignore && cat /tmp/spring-api/.dockerignore

You will see:

.git
*.md
__pycache__
node_modules
target
bin
obj

What it means: Lean context.

Example 14: count

Type this:

wc -l /tmp/spring-api/.dockerignore

You will see:

7 /tmp/spring-api/.dockerignore

What it means: Seven lines.

Example 15: mode

Type this:

stat -c '%a' /tmp/spring-api/.dockerignore

You will see:

644

What it means: Safe.

Step 6: Build the image

Why: build creates the image layers. We do this before the next step so mistakes stay small and easy to undo. Read the output; the names and numbers tell you if the machine is healthy.

What: docker build -t spring-api:1.0. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 16: build

Type this:

docker build -t spring-api:1.0 /tmp/spring-api

You will see:

Successfully tagged spring-api:1.0

What it means: Built.

Example 17: images

Type this:

docker images spring-api --format '{{.Repository}}:{{.Tag}}'

You will see:

spring-api:1.0

What it means: Listed.

Example 18: hist

Type this:

docker history spring-api:1.0 | head -3

You will see:

IMAGE CREATED CREATED BY SIZE

What it means: Layers.

4. Automate and verify

Step 7: Run the container

Why: run starts a live container. We do this before the next step so mistakes stay small and easy to undo. Read the output; the names and numbers tell you if the machine is healthy.

What: docker run -d --name spring-api. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 19: run

Type this:

docker rm -f spring-api >/dev/null 2>&1; docker run -d --name spring-api -p 8080:8080 spring-api:1.0 && echo started

You will see:

started

What it means: Detached.

Example 20: ps

Type this:

docker ps --filter name=spring-api --format '{{.Names}} {{.Ports}}'

You will see:

spring-api 0.0.0.0:8080->8080/tcp

What it means: Mapped.

Example 21: sleep

Type this:

sleep 2; echo up

You will see:

up

What it means: Warmup.

Step 8: Map the port

Why: Host port 8080 reaches the app. We do this before the next step so mistakes stay small and easy to undo. Read the output; the names and numbers tell you if the machine is healthy.

What: -p 8080:8080. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 22: port

Type this:

ss -ltn | grep ':8080 ' | head -1 || echo listening_via_docker

You will see:

LISTEN ... :8080

What it means: Port open.

Example 23: fmt

Type this:

docker port spring-api

You will see:

8080/tcp -> 0.0.0.0:8080

What it means: Publish map.

Example 24: names

Type this:

docker inspect -f '{{.Name}}' spring-api

You will see:

/spring-api

What it means: Name.

Step 9: curl health

Why: /actuator/health proves ready. We do this before the next step so mistakes stay small and easy to undo. Read the output; the names and numbers tell you if the machine is healthy.

What: curl returns 200. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 25: health

Type this:

curl -s http://127.0.0.1:8080/actuator/health

You will see:

{"status":"UP"}

What it means: Actuator UP.

Example 26: code

Type this:

curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/actuator/health

You will see:

200

What it means: OK.

Example 27: hello

Type this:

curl -s http://127.0.0.1:8080/api/hello

You will see:

{"msg":"pay-api"}

What it means: Route.

5. Harden and accept

Step 10: Read logs

Why: Logs show listen lines. We do this before the next step so mistakes stay small and easy to undo. Read the output; the names and numbers tell you if the machine is healthy.

What: docker logs tail. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 28: logs

Type this:

docker logs spring-api 2>&1 | tail -5

You will see:

listen or Started

What it means: App logged.

Example 29: follow

Type this:

echo 'docker logs -f spring-api'

You will see:

docker logs -f spring-api

What it means: Follow tip.

Example 30: inspect

Type this:

docker inspect -f '{{.State.Status}}' spring-api

You will see:

running

What it means: Running.

Step 11: Exec inside

Why: exec is for quick checks. We do this before the next step so mistakes stay small and easy to undo. Read the output; the names and numbers tell you if the machine is healthy.

What: docker exec. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 31: exec

Type this:

docker exec spring-api sh -c 'echo inside'

You will see:

inside

What it means: Shell works.

Example 32: pwd

Type this:

docker exec spring-api pwd

You will see:

/app

What it means: WORKDIR.

Example 33: id

Type this:

docker exec spring-api id | head -c 40; echo

You will see:

uid=...

What it means: User inside.

Step 12: Acceptance checklist

Why: Dev signs off when these pass. We do this before the next step so mistakes stay small and easy to undo. Read the output; the names and numbers tell you if the machine is healthy.

What: All green checks. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 34: smoke

Type this:

curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/actuator/health

You will see:

200

What it means: Smoke.

Example 35: mode

Type this:

stat -c '%a' /tmp/spring-api/Dockerfile

You will see:

644

What it means: Safe.

Example 36: Sign-off

Type this:

echo 'Dev: PASS'

You will see:

Dev: PASS

What it means: Done.

Check yourself

Q1. Base image?

eclipse-temurin:21-jre-alpine

Q2. Host port?

8080

Q3. Health path?

/actuator/health

Q4. Who fixed PhonePe?

Dev

Q5. Build command?

docker build

Q6. Why pin?

So builds stay repeatable

Friends, you dockerized a Spring Boot app with a pinned image. Did you understand? Let us try it once more. Change the health text, rebuild as 1.1, and switch the container.

Lab: try at home

  1. Write the app files.
  2. Write Dockerfile with pinned tag.
  3. Build 1.0.
  4. Run and curl.
  5. Tag 1.1 after a text change.

Recap

  • Pin base image tags.
  • COPY only what you need.
  • -p maps host to container.
  • Same image on every host.
  • Logs prove listen.