กลับไปหน้าบทความ
#Docker#Multi-stage Build#DevOps#CI/CD#Container

Multi-stage Build เทคนิคย่อขนาด Docker Image ที่คุ้มทั้งเร็วและปลอดภัย

Multi-stage Build เป็นเทคนิคสำคัญที่ช่วยลดขนาด Docker image ได้อย่างมาก โดยแยกขั้นตอน build ออกจาก runtime เพื่อนำเข้าเฉพาะสิ่งที่จำเป็นต่อการรันจริง ผลลัพธ์คือ deploy เร็วขึ้น ลดค่าใช้จ่าย และช่วยลด attack surface ของระ

17 มิถุนายน 2569อ่านประมาณ 2 นาที

แชร์บทความ

Multi-stage Build เทคนิคย่อขนาด Docker Image ที่คุ้มทั้งเร็วและปลอดภัย

หลายคนเคยเจอสถานการณ์ที่ build Docker image แล้วขนาดไฟล์พุ่งจากหลักร้อย MB ไปจนถึงหลาย GB ทั้งที่เป้าหมายจริงมีแค่ทำให้แอปรันได้ ปัญหานี้ไม่ได้กระทบแค่พื้นที่จัดเก็บ แต่ยังลากยาวไปถึงความเร็วในการ deploy เวลาที่ระบบ CI/CD ต้องใช้ในการส่งต่อ image และต้นทุนสะสมในระบบที่ต้องดึง image อยู่บ่อย ๆ

หนึ่งในวิธีที่คุ้มค่าที่สุดสำหรับการแก้ปัญหานี้คือ Multi-stage Build ซึ่งไม่ใช่แค่ลูกเล่นในการเขียน Dockerfile ให้ดูดี แต่เป็นแนวทางที่ช่วยตัดสิ่งไม่จำเป็นออกจาก image สำหรับใช้งานจริงได้อย่างมีประสิทธิภาพ

Multi-stage Build คืออะไร

แนวคิดของ Multi-stage Build นั้นตรงไปตรงมา

  • ขั้นแรก ใช้ image ที่มีเครื่องมือพร้อมสำหรับการ build เช่น compiler, package manager, dev dependencies และ source code
  • ขั้นสุดท้าย คัดลอกเฉพาะไฟล์ที่จำเป็นต่อการรันจริงไปยัง image ที่เล็กกว่า

ผลคือ runtime image ไม่ต้องแบกเครื่องมือและไฟล์จำนวนมากที่มีประโยชน์เฉพาะตอน build เท่านั้น

ทำไมขนาด Docker image จึงสำคัญ

หลายทีมมักโฟกัสเพียงว่า container ต้องรันได้ แต่ขนาด image ส่งผลโดยตรงในหลายมิติ

  • Deploy ช้าลง เพราะต้อง push และ pull image ที่ใหญ่เกินจำเป็น
  • เปลือง storage ทั้งในเครื่องนักพัฒนา, CI runner และ container registry
  • เพิ่มภาระให้ CI/CD ทุกครั้งที่ pipeline ต้อง build, upload หรือแจกจ่าย image
  • Scale ระบบช้าลง โดยเฉพาะเมื่อมีหลาย container ต้องเริ่มพร้อมกัน

ยิ่งใน Kubernetes หรือระบบที่ deploy บ่อย ความต่างของขนาด image จะกลายเป็นต้นทุนจริงในระยะยาว

ตัวอย่างผลลัพธ์ที่เห็นชัด

กรณีของ Go

แอป Go เป็นตัวอย่างที่ชัดมาก เพราะมัก compile ออกมาเป็น binary เดี่ยวได้

หาก build แบบตรงไปตรงมา บางคนอาจใช้ golang image รันทั้งระบบ ซึ่งขนาดสามารถแตะประมาณ 800MB ถึง 1GB ได้ไม่ยาก แต่ถ้าเปลี่ยนมาใช้ Multi-stage Build โดย

  • stage แรกใช้ golang สำหรับ compile
  • stage สุดท้ายใช้ alpine หรือ distroless สำหรับรัน binary เพียงอย่างเดียว

ขนาด image อาจลดลงเหลือเพียงประมาณ 10MB ถึง 30MB เท่านั้น หรือคิดเป็นการลดลงระดับ 90% ถึง 99% ในบางกรณี

กรณีของ Node.js

ฝั่ง Node.js ก็เห็นผลชัดไม่แพ้กัน เพราะหลายโปรเจกต์มีของสะสมจำนวนมาก เช่น

  • node_modules
  • devDependencies
  • build tools
  • cache
  • source code ที่ไม่จำเป็นตอน runtime

ถ้าแยก stage สำหรับ install และ build แล้ว copy เฉพาะ dist พร้อม production dependencies ไปยัง final image ขนาด image ที่เดิมประมาณ 600MB อาจลดเหลือ 150MB หรือต่ำกว่านั้น ได้

ภาษาและแพลตฟอร์มอื่น ๆ

สำหรับ Python, Java, .NET หรือ frontend ที่ต้อง compile asset ก่อนใช้งาน ก็มักได้ประโยชน์ชัดเจนเช่นกัน เพราะขั้นตอน build ของ stack เหล่านี้มักมีเครื่องมือและไฟล์ชั่วคราวจำนวนมาก

Multi-stage Build ช่วยมากกว่าแค่เรื่องขนาด

ข้อดีของเทคนิคนี้ไม่ได้มีแค่การลดขนาด image เท่านั้น

1. ลด attack surface

เมื่อ final image ไม่มี compiler, shell tools หรือ package manager ที่ไม่จำเป็น พื้นที่ที่ผู้โจมตีอาจใช้ประโยชน์ก็ลดลงตามไปด้วย นี่ทำให้ image ที่ได้มีความปลอดภัยมากขึ้นโดยธรรมชาติ

2. เร่งความเร็วในการ pull image

หาก image ลดจาก 900MB เหลือ 80MB ความเร็วในการดึง image ขึ้นเครื่องใหม่จะต่างกันอย่างมีนัยสำคัญ โดยเฉพาะในระบบที่ต้อง scale หลาย instance พร้อมกัน

3. ทำให้ pipeline เป็นระเบียบขึ้น

การแยก build stage ออกจาก runtime stage บังคับให้ทีมต้องคิดชัดขึ้นว่าอะไรคือสิ่งที่จำเป็นต่อการรันจริง ส่งผลให้ Dockerfile อ่านง่าย ดูแลง่าย และ optimize ต่อได้ง่ายกว่าเดิม

ตัวอย่างโครงสร้าง Dockerfile แบบย่อ

แนวคิดพื้นฐานของ Dockerfile แบบ Multi-stage Build อาจหน้าตาประมาณนี้

FROM node:20 AS builder
# install dependency และ build แอป

FROM node:20-alpine AS runner
# copy เฉพาะ build output และ dependency ที่จำเป็น
CMD ["node", "server.js"]

จุดสำคัญไม่ได้อยู่ที่มี FROM หลายชุดเท่านั้น แต่คือการตัดสินใจว่า จะ copy อะไรเข้า final image บ้าง หากยังคง copy source ทั้งหมดหรือยก node_modules มาทั้งก้อนโดยไม่คัดกรอง ขนาดที่ลดลงอาจไม่ต่างจากเดิมมากนัก

เทคนิคที่ควรใช้ร่วมกัน

เพื่อให้ Multi-stage Build ได้ผลดีที่สุด ควรจับคู่กับแนวทางเหล่านี้

  • ใช้ .dockerignore เพื่อตัดไฟล์ที่ไม่เกี่ยวข้อง เช่น .git, logs, local cache และ test files
  • เลือก base image ที่เล็กและเหมาะกับงาน
  • ติดตั้งเฉพาะ production dependencies
  • รวมคำสั่ง RUN เท่าที่จำเป็นเพื่อลด layer ที่ฟุ่มเฟือย
  • ระวังการใช้ alpine ในบางภาษา เพราะแม้จะเล็ก แต่บางไลบรารีอาจมีปัญหา compatibility

ข้อจำกัดที่ควรรู้

แม้ Multi-stage Build จะมีประโยชน์มาก แต่ก็ไม่ใช่ยาวิเศษสำหรับทุกปัญหา

บางทีมลดขนาด image ได้มากแล้ว แต่ deploy ยังช้าอยู่ สาเหตุอาจมาจากปัจจัยอื่น เช่น

  • จำนวน layer ของ image
  • ความเร็วเครือข่าย
  • ประสิทธิภาพของ registry
  • ขั้นตอน startup ของแอปเอง

นั่นหมายความว่า Multi-stage Build เป็นการปรับปรุงที่คุ้มมาก แต่ควรมองร่วมกับภาพรวมของระบบเสมอ

ควรเริ่มอย่างไร

หากยังไม่เคยใช้ Multi-stage Build วิธีเริ่มต้นที่ดีคือ

  1. แยก stage สำหรับ build ออกจาก stage สำหรับ runtime
  2. copy เฉพาะไฟล์ที่จำเป็นต่อการรันจริง
  3. ตรวจสอบ dependencies ว่ามีส่วนไหนเป็น dev-only และไม่ควรเข้า final image
  4. ใช้ .dockerignore ควบคู่กันเพื่อลดขนาด build context
  5. ค่อย ๆ optimize เพิ่มทีละจุดจากผลลัพธ์จริง

สรุป

Multi-stage Build เป็นหนึ่งในเทคนิคที่คุ้มค่าที่สุดสำหรับการปรับปรุง Docker image เพราะช่วยลดขนาดได้จริง และในหลายกรณีอาจลดลงได้เกิน 90% โดยเฉพาะแอปที่มีขั้นตอน compile หนัก นอกจากจะช่วยให้ deploy เร็วขึ้นและลดต้นทุน storage แล้ว ยังช่วยลด attack surface และทำให้ pipeline มีความเป็นระเบียบมากขึ้นด้วย

หากยังไม่ได้ใช้ การเริ่มจากการแยก build stage กับ runtime stage คือก้าวแรกที่ให้ผลตอบแทนสูงมาก และบางครั้งการปรับ Dockerfile เพียงครั้งเดียว ก็อาจช่วยประหยัดเวลาของทั้งทีมได้ทุกวัน