Backend

99 Ngày Spring — Ngày 89: Docker hóa Spring Boot

SSite Admin
23 tháng 10, 2026 3 phút đọc 7 lượt xem
99 Ngày Spring — Ngày 89: Docker hóa Spring Boot

Đóng một file jar vào image chỉ cần ba dòng Dockerfile, nhưng image đó thường to, build lại chậm và chạy bằng quyền root. Một Dockerfile chuẩn cho Spring Boot giải quyết ba việc: tách môi trường build khỏi môi trường chạy, chia jar thành các layer để tận dụng cache, và chạy bằng người dùng không có đặc quyền.

Docker hóa Spring Boot

1. Nhiều stage: build một nơi, chạy một nơi

Stage đầu dùng image có JDK để biên dịch; stage cuối chỉ cần JRE và những file phải có lúc chạy. Mã nguồn, Maven và cache build không đi vào image cuối, nên image nhỏ hơn và ít bề mặt tấn công hơn. Ghim tag của base image theo phiên bản cụ thể để build lặp lại được, và thêm .dockerignore để không gửi target, .git vào build context.

FROM eclipse-temurin:21-jdk AS build
WORKDIR /workspace
COPY . .
RUN ./mvnw -q -DskipTests package

FROM eclipse-temurin:21-jre AS extract
WORKDIR /workspace
COPY --from=build /workspace/target/*.jar app.jar
RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted

2. Layered jar: đổi code không tải lại thư viện

Fat jar là một file duy nhất: sửa một dòng code thì cả trăm megabyte thư viện bị coi là thay đổi. Spring Boot biết chia jar thành bốn layer theo tần suất thay đổi: dependencies, spring-boot-loader, snapshot-dependencies và application. COPY từng layer theo thứ tự đó thì các layer thư viện được lấy từ cache, chỉ layer application nhỏ phải build và đẩy lại.

FROM eclipse-temurin:21-jre
WORKDIR /app
RUN useradd --system --uid 1001 spring
USER spring
COPY --from=extract /workspace/extracted/dependencies/ ./
COPY --from=extract /workspace/extracted/spring-boot-loader/ ./
COPY --from=extract /workspace/extracted/snapshot-dependencies/ ./
COPY --from=extract /workspace/extracted/application/ ./
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "app.jar"]

3. Chạy đúng trong container

Chạy bằng user không phải root để một lỗ hổng trong ứng dụng không trao luôn quyền root trong container. ENTRYPOINT viết dạng mảng để tiến trình java nhận trực tiếp tín hiệu SIGTERM; kết hợp server.shutdown=graceful, ứng dụng xử lý xong request đang dở trước khi tắt. JVM tự nhận giới hạn bộ nhớ của container; MaxRAMPercentage chừa chỗ cho phần bộ nhớ ngoài heap.

  • Không đưa secret vào image hay ARG; truyền lúc chạy.

  • Cấu hình đến từ biến môi trường, cùng một image dùng cho mọi môi trường.

  • Dùng health endpoint của Actuator cho probe của nền tảng chạy container.

4. Buildpacks: không cần Dockerfile

Plugin Spring Boot có thể tạo image OCI bằng Cloud Native Buildpacks chỉ với một lệnh. Buildpack tự chọn JRE, chia layer, chạy bằng user không phải root và tính sẵn tham số bộ nhớ cho JVM. Bạn mất một phần quyền kiểm soát chi tiết nhưng được một image theo thông lệ tốt mà không phải bảo trì Dockerfile.

./mvnw spring-boot:build-image \
    -Dspring-boot.build-image.imageName=registry.example/orders:1.4.0
docker run --rm -p 8080:8080 \
    -e SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/shop \
    registry.example/orders:1.4.0

Một image tốt nhỏ, build lại nhanh, không chứa secret và không chạy bằng root. Ngày 90 dùng Docker cho việc gần gũi hơn: dựng môi trường dev bằng Docker Compose.

Tài liệu đối chiếu

  • https://docs.spring.io/spring-boot/reference/packaging/container-images/dockerfiles.html

  • https://docs.spring.io/spring-boot/reference/packaging/container-images/cloud-native-buildpacks.html

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Java — Ngày 89: Mockito

mock, stub và verify, @Mock với @InjectMocks, ArgumentCaptor và những gì không nên mock.

23 thg 10, 20263 phút6
99 Ngày Spring — Ngày 88: GraalVM Native Image

AOT processing, cách build native executable, giả định thế giới đóng và những hạn chế với reflection.

23 thg 10, 20263 phút5
99 Ngày Java — Ngày 88: Unit Test với JUnit 5

@Test và vòng đời, các assertion quan trọng, parameterized test và cách viết test đọc được như đặc tả.

23 thg 10, 20263 phút5