Sự kiện Dockerfile Contest 2025 do Devops Việt Nam tổ chức đã nhanh chóng trở thành một trong những đấu trường khắc nghiệt nhất, nơi các chuyên gia Container và DevOps thể hiện nghệ thuật tối ưu hóa triệt để. Yêu cầu của cuộc thi không chỉ dừng lại ở việc build nhanh, mà còn phải đạt đến sự hoàn hảo về bảo mật, kích thước image tinh gọn, và hiệu suất runtime đỉnh cao, đặc biệt với các ứng dụng Java/Spring Boot nổi tiếng là khó nén.
Trong cuộc thi này, một cái tên đã gây bất ngờ lớn: anh Huỳnh Quang Tiên dành Top 1 dự án Java. Bằng sự kết hợp giữa triết lý tối giản và sự tìm tòi sâu sắc vào cấu trúc của JVM, anh đã trình bày một Dockerfile được xem là bậc thầy về tinh gọn hóa.
Bài phỏng vấn độc quyền dưới đây sẽ hé lộ những bí quyết kỹ thuật mà anh Tiên đã áp dụng từ việc tạo ra custom JRE bằng JLink cho đến chiến lược Layer Caching thông minh mang lại bài học quý giá cho toàn bộ cộng đồng Java và DevOps.

Anh đã tham gia vào lĩnh vực tối ưu hóa Java/Container như thế nào? Theo anh, đâu là thách thức lớn nhất khi container hóa một ứng dụng Java/Spring Boot truyền thống, và triết lý chính của anh khi bắt đầu tối ưu hóa Dockerfile này là gì?
Thực ra, mình không phải là một chuyên gia DevOps. Mình đã làm việc với các ứng dụng Java nhiều năm và có sử dụng Docker cho mục đích cá nhân, nhưng chưa từng làm việc chuyên sâu về DevOps. Việc tham gia cuộc thi này là một cơ hội để mình tìm hiểu sâu hơn về mảng tối ưu hóa.
Theo mình, thách thức lớn nhất khi container hóa một ứng dụng Java truyền thống là bản chất của Java phải chạy qua JVM. Việc đóng gói một ứng dụng Java vào container có thể được ví von như đóng gói một máy ảo vào một máy ảo khác, điều này có thể ảnh hưởng đến hiệu năng so với các ngôn ngữ biên dịch trực tiếp.
Một cái nữa là sự khác biệt của những cái Build Tool dành cho Java. Bản thân mình thì thường xuyên xài Maven cho dự án của mình, chỉ thỉnh thoảng xài Gradle khi dự án đó bắt buộc phải xài. Nên các tùy chỉnh tối ưu của 2 Build Tool có thể khác nhau, làm mình phải dành thời gian tìm hiểu khá nhiều về chính các cái Build Tool để có thể làm sao tối ưu được thời gian build.
Việc container hóa một ứng dụng Java có thể giống như việc đóng gói một máy ảo vào một máy ảo khác. Nên khi bắt đầu tối ưu dockerfile này, mình sẽ bắt đầu với việc làm sao để cái dockerfile hoạt động với những image được quảng bá là bảo mật tuyệt đối, nhẹ nhất và nhanh nhất. Sau đó xem có thể tối ưu chính cái ứng dụng sẽ chạy được bao nhiêu. Cuối cùng mình sẽ xem xét xem có thể tối ưu cả cái JVM được nữa không.
Trong Dockerfile chiến thắng, Anh đã áp dụng một kỹ thuật rất cao cấp là tạo ra một Môi trường Runtime Java tùy chỉnh (custom JRE) bằng công cụ JLink. Ý tưởng cốt lõi đằng sau chiến lược này là gì, và nó đã giải quyết vấn đề kích thước phình to và thời gian khởi động của Java Application Image một cách hiệu quả như thế nào?
Sau khi đã tìm đủ mọi cách để tối ưu hóa các phần khác mà không thể giảm thêm kích thước image, mình bắt đầu nghĩ đến việc liệu có thể tối ưu chính JRE hay không? Ý tưởng cốt lõi đến từ việc nghiên cứu những image đã được tối ưu JRE và các bài viết liên quan đến vấn đề này, và họ đề xuất là sẽ tự tạo một cái JRE bằng JLink để chỉ giữ những module cần thiết để chạy ứng dụng này thôi.
Chiến lược của mình là tự tạo một JRE tùy chỉnh bằng JLink và sử dụng JDeps để chỉ giữ lại những module Java cần thiết tuyệt đối để chạy ứng dụng của mình. Thế mình đã kết hợp hai công cụ này, đồng thời thử nghiệm các tùy chọn để xóa các thành phần không cần thiết và tùy chọn nén tệp.
Kết quả là tạo ra một JRE siêu tinh gọn, đảm bảo ứng dụng có thể chạy được. Việc loại bỏ hàng loạt module không sử dụng đã giúp giảm đáng kể kích thước image, từ đó giảm thời gian kéo image và cải thiện thời gian chạy.
Anh đã chọn một base image runtime thuộc dòng Distroless (một image cực kỳ tinh gọn, không có shell và package manager). Quyết định này nhấn mạnh vào yếu tố bảo mật. Ngoài yếu tố bảo mật, việc kết hợp Custom JRE với base image tinh gọn này còn mang lại lợi ích gì về hiệu suất? Anh đã chuẩn bị những gì để đảm bảo ứng dụng vẫn chạy ổn định trong môi trường tối giản như vậy?
Về mặt hiệu suất, mình tin rằng bất kỳ giải pháp nào giảm thiểu các bước trung gian và loại bỏ các thành phần không cần thiết đều là ưu tiên hàng đầu trong môi trường production. Việc kết hợp Custom JRE siêu tinh gọn với base image Distroless – một image vốn đã không có shell hay package manager là cách để đạt được sự tối giản tuyệt đối.
Về việc chuẩn bị thì trước khi mình biết tới JLink, mình đã sử dụng distroless và thử nghiệm trước xem nó có thể chạy được ứng dụng Java hay không. Sau khi mình đã thấy nó chạy được thì mình mới nghĩ tới việc tối ưu môi trường Java, và thế là mình tìm hiểu JLink. Sau khi tối ưu được JRE bằng JLink thì mình chạy lại một lần nữa trên Distroless Base Image để chắc chắn là nó vẫn chạy được ứng dụng.
Việc phân chia các lệnh copy (copy Gradle wrapper, sau đó copy Source Code) là một chiến lược quan trọng để tăng tốc độ build. Anh có thể chia sẻ nguyên tắc vàng của mình khi tổ chức các layers trong Dockerfile Java/Spring Boot để tối ưu Layer Caching, đặc biệt là khi làm việc trong môi trường CI/CD không?
Mình nghĩ nguyên tắc vàng chỉ đơn thuần là kinh nghiệm được rút ra sau những lần build mất quá nhiều thời gian. Đối với Gradle, mình thường xuyên gặp tình trạng công cụ này tự động tải lại khi chỉ thay đổi source code. Vì vậy, kinh nghiệm là phải tách biệt việc tải và cache Gradle trước khi build source code.
Nguyên tắc chung là chỉ copy từng phần của dự án, ưu tiên copy những file ít bị thay đổi trước để tận dụng tối đa Layer Cache. Điều này đảm bảo rằng khi có sự thay đổi nhỏ trong source code, Docker chỉ cần build lại những layer từ đó trở đi, tối ưu hóa đáng kể thời gian build trong môi trường CI/CD.
Một chi tiết thú vị là việc tạo thêm một Stage phụ chỉ để copy một công cụ đơn giản (như wget) phục vụ cho Healthcheck trong Stage Runtime. Tại sao Anh lại chọn chiến lược này thay vì sử dụng các công cụ có sẵn hoặc các phương pháp kiểm tra khác? Nó thể hiện tư duy gì trong việc tối ưu layer image?
Đơn giản là Distroless không chứa bất kỳ một công cụ nào cả, kể cả những công cụ để Healthcheck bằng URL như curl hay wget. Mình cũng không muốn xài nhánh debug của nó do đang giả tưởng là sẽ chạy production và nhánh debug có shell tạo ra một nơi có thể bị tấn công. Mình có tìm hiểu sâu hơn về cách healthcheck trên distroless thì có vài người gợi ý là sẽ copy wget hoặc curl từ một image có sẵn như busybox vào stage runtime.
Mình cũng có nghĩ tới chuyện sẽ tạo ra một đoạn code để Healthcheck URL bằng Java nhưng mình đã không làm vì mình không muốn Dockerfile của mình quá phức tạp, và việc tự tạo ra đoạn code java đó làm tăng thời gian build Dockerfile và tăng số lượng Layer không cần thiết.
Sau khi suy nghĩ thì mình chọn copy wget từ busybox qua stage Runtime do bản thân nó có thể tự chạy mà không cần các thành phần bên ngoài, nó đủ nhẹ và mình thêm chỉ một layer chắc chắn có thể cache lại được.
Sau cuộc thi này, bài học quan trọng nhất mà Anh muốn chia sẻ với cộng đồng Java/DevOps là gì? Và Anh đánh giá thế nào về tương lai của Containerization Java với các công nghệ như GraalVM Native Image? Anh đang quan tâm đến những xu hướng công nghệ nào tiếp theo?
Nhìn chung thì cái Dockerfile của mình cũng không có gì đặc biệt, chỉ vận dụng một vài cái best practice mà mình tìm được trong quá trình tham gia cuộc thi này. Bài học của mình là luôn tìm hiểu những công nghệ mới và phải luôn dựa vào dữ liệu thực tế trước khi đưa giả thuyết của mình. Việc mình biết JLink, JDeps và Distroless là do mình hay lướt những dự án mới và những dự án tìm năng có thể giúp ích cho công việc của mình.
Mình có nghe về GraalVM Native Image nhưng với Spring thì mình nghĩ nó vẫn chưa ổn định do bản chất của Spring vẫn là xài Reflection và nó là điểm yếu của GraalVM, đồng thời thời gian build cũng sẽ lâu hơn và tiêu tốn tài nguyên nhiều hơn. Nên hiện tại chắc sẽ luôn là những giải pháp tối ưu môi trường Java, thời gian build, tốc độ chạy ứng dụng, và tài nguyên chạy sẽ luôn được ưu tiên.
Bản thân mình theo phong cách tối giản nên mình luôn quan tâm tới những xu hướng giúp đơn giản hóa quá trình làm việc của mình, và những xu hướng tối ưu ứng dụng, làm cho ứng dụng có thể chạy được trên những môi trường hạn chế tài nguyên. Xu hướng mình hướng tới luôn là những công nghệ có tính bền vững, có thể bảo trì lâu dài. Vì mình mong muốn công nghệ phải luôn có mặt cho tất cả mọi người, không quan trọng tài nguyên, và luôn phải bền vững để mình vẫn có thể sử dụng vào 10 năm sau.

Thành công của anh Huỳnh Quang Tiên tại Dockerfile Contest 2025 là một minh chứng sức mạnh của tư duy tối giản và kỹ thuật cao cấp. Chiến lược cốt lõi của anh kết hợp việc tạo môi trường Custom JRE bằng JLink và sử dụng nền tảng Distroless đã tái định nghĩa khả năng thu nhỏ và bảo mật của ứng dụng Java/Spring Boot trong môi trường container
Những chia sẻ sâu sắc từ anh Tiên không chỉ cung cấp các best practice về Layer Caching hay Healthcheck, mà còn truyền tải thông điệp về việc không ngừng tìm hiểu công nghệ mới và luôn dựa vào dữ liệu thực tế trước khi tối ưu hóa. Đây là đóng góp vô giá cho cộng đồng DevOps Việt Nam và mọi tổ chức đang tìm kiếm giải pháp tối ưu hóa chi phí và hiệu suất cho ứng dụng Java.
Chúng tôi xin chân thành cảm ơn anh Huỳnh Quang Tiên và chúc anh tiếp tục theo đuổi con đường công nghệ bền vững, mang lại giá trị cho cộng đồng.
Tham khảo chi tiết Dockerfile của anh Tiên tại: Dockerfile Contest 2025











