Docker Base Image: Fat vs Slim vs Alpine

Trong quá trình review Dockerfile cho team dev, mình thấy một thói quen rất phổ biến là dùng ngay FROM ubuntu:latest hoặc FROM python:latest. Đúng ra là phải share cho anh em cái Dockerfile Contest 2025 vừa rồi sẽ ngộ ra nhiều thứ ngay.

Tư duy này thường xuất phát từ sự tiện lợi: “Cứ cài bản full cho đầy đủ thư viện, đỡ phải cài thêm lắt nhắt, chạy được là được.” Các bạn dev có thể hơi thiếu hơn kiến thức bảo mật và vận hành hệ thống hơn các bạn sysadmin và chắc chắn ngược lại các bạn sysadmin khả năng code sẽ không bằng các bạn dev, nên chúng ta tập trung vào đúng giá trị nha. Vì vậy, khi ra môi trường Production, cái sự “tiện lợi” đó sẽ trả giá bằng thời gian deploy, Registry cost và nghiêm trọng hơn là Attack Surface.

Bài này coi như là một chút thực tế cho các bạn cực kỳ newbie…

So sánh hiệu năng Docker Base Image: Fat, Slim và Alpine

Bài toán

Để bài test công bằng, mình sẽ build một ứng dụng Python đơn giản. Ứng dụng này chỉ import một vài thư viện phổ biến và in ra dòng “Hello World”.

Các đối thủ tham gia benchmark bao gồm:

  1. Fat Image (python:3.11): Dựa trên Debian đầy đủ. Chứa đủ thứ tool từ git, curl đến build-essential.
  2. Slim Image (python:3.11-slim): Phiên bản rút gọn của Debian. Đã lược bỏ các package không thiết yếu, chỉ giữ lại những gì tối thiểu để chạy Python.
  3. Alpine Image (python:3.11-alpine): Dựa trên Alpine Linux. Sử dụng musl libc thay vì glibc truyền thống. Nổi tiếng là siêu nhẹ.

Mục tiêu của mình là đo đạc 3 chỉ số quan trọng: Image Size, Pull Time và Vulnerability Count.

Phân tích

Trước khi xem kết quả, mình muốn phân tích một chút về bản chất kỹ thuật của các image này.

  • Fat Image: Đây là lựa chọn an toàn cho môi trường Development. Nó có sẵn compiler và header file. Nếu các bạn cài một thư viện Python cần biên dịch C extension (ví dụ numpy hay pandas đời cũ), nó sẽ chạy mượt mà. Nhưng đổi lại, nó mang theo cả một hệ điều hành Debian cồng kềnh.
  • Slim Image: Đây là điểm Sweet spot. Nó vẫn dùng glibc, nên tính tương thích binary cực tốt. Hầu hết các thư viện Python trên PyPI đều hỗ trợ tốt. Nó nhẹ hơn Fat rất nhiều nhưng thiếu các build tool.
  • Alpine Image: Đây là lựa chọn tối ưu cho kích thước. Nhưng Alpine dùng musl libc. Điều này có nghĩa là nhiều binary wheel compiled cho glibc sẽ không chạy được. Pip sẽ phải tự động compile lại từ source, dẫn đến việc build image lâu hơn và đôi khi gặp lỗi biên dịch rất khó chịu nhất là với các thư viện liên quan đến gRPC hay Machine Learning.

Benchmark

Mình thực hiện build 3 Dockerfile tương ứng và dùng công cụ trivy để quét lỗi bảo mật.

Kịch bản test:

  1. Build image.
  2. Kiểm tra dung lượng bằng docker images.
  3. Quét lỗi bảo mật bằng trivy image.
  4. Đo thời gian xóa image local và pull lại trên đường truyền mạng tiêu chuẩn.

Kết quả đo đạc

Đây là số liệu mình thu được, số liệu có thể dao động tùy thời điểm update của image:

1. Fat Image (python:3.11)

  • Image Size: ~1.01 GB
  • Vulnerability (High/Critical): ~300+ lỗi, bao gồm lỗi hệ thống Debian chưa vá.
  • Pull Time: ~45 giây.

2. Slim Image (python:3.11-slim)

  • Image Size: ~154 MB
  • Vulnerability (High/Critical): ~40 lỗi.
  • Pull Time: ~8 giây.

3. Alpine Image (python:3.11-alpine)

  • Image Size: ~58 MB
  • Vulnerability (High/Critical): 0 hoặc rất ít, thường < 5.
  • Pull Time: ~3 giây.

Key Findings

Nhìn vào số liệu trên ta có thể thấy.

  1. Về dung lượng: Fat image nặng gấp 6.5 lần so với Slim và gấp 17 lần so với Alpine. Trong môi trường Kubernetes, khi Node phải thực hiện Scale-out, việc pull 1GB dữ liệu qua mạng sẽ làm chậm quá trình Pod startup đáng kể, đặc biệt khi Registry throttling.
  2. Về bảo mật: Fat image chứa hàng trăm công cụ thừa thãi như netcat, curl, compiler. Nếu Hacker chiếm được quyền điều khiển Container, hắn có sẵn một kho vũ khí để tấn công các service khác trong mạng. Alpine và Slim giảm thiểu Attack Surface này đi rất nhiều.
  3. Về tính tương thích: Mặc dù Alpine nhẹ nhất, nhưng kinh nghiệm cho thấy Alpine thường gây đau đầu với các vấn đề về DNS resolver và performance của musl memory allocator kém hơn glibc trong một số workload cao.

Kết luận

Từ bài benchmark này, mình rút ra vài nguyên tắc khi xây dựng Image Production:

  • Tuyệt đối không dùng Fat Image cho Production: Trừ khi các bạn có lý do cực kỳ đặc biệt. Việc lãng phí băng thông và tài nguyên lưu trữ là không thể chấp nhận được ở quy mô lớn.
  • Ưu tiên Slim Image: Với mình, Debian Slim là sự lựa chọn cân bằng nhất. Nó đủ nhỏ khoảng 150MB là chấp nhận được, bảo mật tốt hơn bản Fat, và quan trọng nhất là tính tương thích tuyệt vời với hệ sinh thái Python/NodeJS vốn dựa nhiều vào glibc.
  • Cẩn trọng với Alpine: Chỉ dùng Alpine nếu các bạn thực sự hiểu về musl libc và sẵn sàng xử lý các lỗi build time phức tạp. Đừng chọn Alpine chỉ vì nó nhẹ hơn Slim 100MB nếu phải đánh đổi bằng hàng giờ debug lỗi missing shared library.
  • Áp dụng Multi-stage Build: Các bạn có thể dùng Fat image ở stage builder để biên dịch code, sau đó copy binary hoặc thư viện sang stage final dùng Slim hoặc Alpine. Như vậy chúng ta vừa build dễ dàng, vừa có image cuối cùng siêu nhẹ.
  • Luôn quét Vulnerability: Tích hợp trivy hoặc các công cụ quét Image vào CI/CD Pipeline. Đừng để một image chứa 300 lỗi Critical lọt lên Production.

Thông tin nổi bật

Sự kiện phát trực tiếp​

Event Thumbnail

Báo cáo quan trọng

Article Thumbnail
Article Thumbnail
Chia sẻ bài viết:
Theo dõi
Thông báo của
0 Góp ý
Được bỏ phiếu nhiều nhất
Mới nhất Cũ nhất
Phản hồi nội tuyến
Xem tất cả bình luận

Tiêu điểm chuyên gia