Docker Multi-stage Build คู่มือสมบูรณ์ — ลดขนาด Image 90% ในปี 2026

Docker Multi-stage Build คู่มือสมบูรณ์ เป็นหัวข้อที่ได้รับความสนใจอย่างมากในวงการ IT บทความนี้อธิบายหลักการทำงาน วิธีติดตั้ง และ best practices สำหรับการใช้งานจริง
สารบัญ
- Multi-stage Build คืออะไร
- ปัญหาของ Single-stage Build
- โครงสร้าง Multi-stage Dockerfile
- ตัวอย่างจริง: Go Application
- ตัวอย่างจริง: Node.js Application
- ตัวอย่างจริง: Python FastAPI
- ตัวอย่างจริง: Java Spring Boot
- เทคนิค Advanced — Named Stages และ Build Arguments
- Multi-stage กับ CI/CD Pipeline
- Best Practices 10 ข้อ
- เปรียบเทียบ Base Image ยอดนิยม
- Debugging Multi-stage Build
- FAQ
Multi-stage Build คืออะไร
Docker Multi-stage Build คือเทคนิคการเขียน Dockerfile ที่อนุญาตให้ใช้ หลาย FROM statement ในไฟล์เดียวแต่ละ FROM จะสร้าง stage ใหม่ขึ้นมาโดย stage แรกๆทำหน้าที่ compile หรือ build application ส่วน stage สุดท้ายจะเป็น production image ที่เก็บเฉพาะ binary หรือ artifact ที่จำเป็นเท่านั้น
แนวคิดนี้ถูกเพิ่มเข้ามาตั้งแต่ Docker 17.05 (ปี 2017) และกลายเป็น standard practice สำหรับ production workload ทุกชนิดในปัจจุบันเพราะช่วยลดขนาด image ได้ 50-95% โดยไม่ต้องเปลี่ยน workflow การ build แต่อย่างใด
ตัวอย่างเปรียบเทียบขนาด image:
| Application | Single-stage | Multi-stage | ลดลง |
|---|---|---|---|
| Go REST API | 1.2 GB | 12 MB | 99% |
| Node.js Express | 950 MB | 150 MB | 84% |
| Python FastAPI | 1.1 GB | 180 MB | 83% |
| Java Spring Boot | 680 MB | 200 MB | 70% |
| React Frontend | 1.5 GB | 25 MB | 98% |
ปัญหาของ Single-stage Build
ก่อนจะมี multi-stage build นักพัฒนามักเขียน Dockerfile แบบ single-stage ซึ่งหมายความว่า ทุกอย่างอยู่ใน image เดียว ตั้งแต่ compiler, build tools, source code, test framework ไปจนถึง production binary สิ่งที่ตามมาคือปัญหาหลายด้าน:
- ขนาด Image ใหญ่เกินจำเป็น — image ที่มี gcc, make, npm devDependencies รวมอยู่ด้วยอาจมีขนาด 1-2 GB ทั้งที่ application จริงๆต้องการแค่ไม่กี่สิบ MB
- Attack Surface กว้าง — ยิ่ง image มี package มากยิ่งมีจุดที่อาจเกิดช่องโหว่ด้านความปลอดภัย CVE (Common Vulnerabilities and Exposures) ที่ scanner ตรวจพบจะมากตามจำนวน package
- Build Cache ไม่มีประสิทธิภาพ — เมื่อเปลี่ยน source code แม้แค่บรรทัดเดียว Docker อาจต้อง rebuild layer ทั้งหมดที่อยู่หลังจุดที่เปลี่ยนทำให้ build ช้า
- Deploy ช้า — ใน Kubernetes cluster ที่มีหลายร้อย node ทุก node ต้อง pull image ใหม่เมื่อ deploy ถ้า image ขนาด 1.5 GB กับ 50 MB เวลาที่ใช้ต่างกันมหาศาล
- Secret Leak — API key หรือ credential ที่ใช้ตอน build อาจติดค้างอยู่ใน layer ของ image ทำให้ผู้ที่เข้าถึง image สามารถดึง secret ออกมาได้
โครงสร้าง Multi-stage Dockerfile
โครงสร้างพื้นฐานของ multi-stage Dockerfile ประกอบด้วย 2 ส่วนหลัก:
# === Stage 1: Builder ===
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server ./cmd/server
# === Stage 2: Production ===
FROM alpine:3.19
RUN apk --no-cache add ca-certificates tzdata
COPY --from=builder /app/server /usr/local/bin/server
EXPOSE 8080
CMD ["server"]
คำสั่งที่สำคัญที่สุดคือ COPY --from=builder ซึ่งเป็นการ คัดลอก artifact จาก stage ก่อนหน้า มาใส่ใน stage ปัจจุบันโดย stage builder จะถูก discard ไปหลัง build เสร็จไม่ถูกรวมเข้าไปใน final image
คุณสามารถมีกี่ stage ก็ได้ตามต้องการเช่น stage สำหรับ test, stage สำหรับ lint, stage สำหรับ build static assets แยกจาก backend เป็นต้น
ตัวอย่างจริง: Go Application
Go เป็นภาษาที่ได้ประโยชน์จาก multi-stage build มากที่สุดเพราะ compile ออกมาเป็น static binary ไม่ต้องพึ่ง runtime ภายนอกสามารถรันบน scratch หรือ distroless image ได้เลย
# Build stage
FROM golang:1.22-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /bin/api ./cmd/api
# Production stage - ใช้ scratch (0 bytes base)
FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /bin/api /api
EXPOSE 8080
ENTRYPOINT ["/api"]
ผลลัพธ์: image สุดท้ายมีขนาดเพียง 8-15 MB เทียบกับ golang:1.22-alpine ที่มีขนาด 250 MB+ และ flag -ldflags="-s -w" ช่วยลดขนาด binary โดยตัด debug information ออก
ตัวอย่างจริง: Node.js Application
สำหรับ Node.js จะแตกต่างจาก Go ตรงที่ต้องมี runtime (Node) อยู่ใน final image ดังนั้นเราจะใช้ multi-stage เพื่อ แยก build dependencies ออกจาก production dependencies
# Stage 1: Install ALL dependencies และ build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN npm prune --production
# Stage 2: Production - เฉพาะ production deps
FROM node:20-alpine
WORKDIR /app
RUN addgroup -g 1001 appgroup && adduser -u 1001 -G appgroup -s /bin/sh -D appuser
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]
จุดสำคัญ: npm prune --production จะลบ devDependencies ทิ้งและเราสร้าง non-root user ด้วย adduser เพื่อความปลอดภัย image สุดท้ายจะมีเฉพาะ compiled code กับ production dependencies เท่านั้น
ตัวอย่างจริง: Python FastAPI
Python ใช้ multi-stage ได้ดีมากโดยเฉพาะเมื่อต้อง compile C extensions ใน stage แรกแล้วคัดลอกเฉพาะ wheel ที่ build เสร็จแล้วไป stage สุดท้าย
# Stage 1: Build wheels
FROM python:3.12-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends gcc libpq-dev
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt
# Stage 2: Production
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels
COPY . .
RUN useradd -r -s /bin/false appuser && chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
เทคนิคสำคัญคือการใช้ pip wheel ใน build stage เพื่อ pre-compile ทุก package เป็น wheel file จากนั้น pip install จาก wheel ใน production stage โดยไม่ต้องมี compiler ทำให้ final image ไม่มี gcc, libpq-dev หรือ header files ใดๆ
ตัวอย่างจริง: Java Spring Boot
Java application มักมีขนาดใหญ่เพราะต้องรวม JDK ไว้ด้วยแต่ด้วย multi-stage เราสามารถใช้ JDK สำหรับ build แต่ใช้ JRE สำหรับ run ได้
# Stage 1: Build with Maven
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
# Stage 2: Run with JRE only
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
RUN addgroup -S spring && adduser -S spring -G spring
COPY --from=builder /app/target/*.jar app.jar
USER spring
EXPOSE 8080
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
JDK มีขนาดประมาณ 400 MB แต่ JRE Alpine มีขนาดเพียง 130 MB และ flag -XX:+UseContainerSupport ทำให้ JVM รู้จัก container memory limit จึงจัดการ heap ได้อย่างเหมาะสม
เทคนิค Advanced — Named Stages และ Build Arguments
นอกจากรูปแบบพื้นฐาน 2 stage แล้ว Docker multi-stage ยังรองรับเทคนิค advanced หลายอย่าง:
Named Stages และ Target Build
# Dockerfile with named stages
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM deps AS test
COPY . .
RUN npm test
FROM deps AS build
COPY . .
RUN npm run build
FROM nginx:alpine AS production
COPY --from=build /app/dist /usr/share/nginx/html
คุณสามารถ build เฉพาะ stage ที่ต้องการด้วย docker build --target test . ซึ่งมีประโยชน์มากใน CI/CD pipeline เพราะแยก test กับ build ออกจากกันได้
Build Arguments สำหรับ Dynamic Base Image
ARG NODE_VERSION=20
FROM node:-alpine AS builder
ARG BUILD_ENV=production
ENV NODE_ENV=
WORKDIR /app
COPY . .
RUN npm ci && npm run build
การใช้ ARG ทำให้ Dockerfile เดียวรองรับได้หลาย version และหลาย environment โดยเปลี่ยนค่าตอน build ด้วย docker build --build-arg NODE_VERSION=18 .
Multi-stage กับ CI/CD Pipeline
Multi-stage build ทำงานร่วมกับ CI/CD ได้อย่างยอดเยี่ยมตัวอย่างการใช้กับ GitHub Actions:
# .github/workflows/build.yml
name: Build and Push
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
with:
context: .
target: production
push: true
tags: ghcr.io/myorg/myapp:}
cache-from: type=gha
cache-to: type=gha, mode=max
สิ่งที่ควรทำใน CI/CD pipeline:
- ใช้ BuildKit cache —
cache-fromและcache-toช่วยให้ build ครั้งถัดไปเร็วขึ้น 3-5 เท่าเพราะ reuse layer จาก build ก่อนหน้า - แยก test stage — รัน test ใน stage แยกถ้า test fail จะไม่ build production image
- Scan image — ใช้เครื่องมือเช่น Trivy หรือ Snyk scan final image เพื่อหา vulnerability ก่อน push ไป registry
- Tag ด้วย git SHA — ทำให้ trace ได้ว่า image มาจาก commit ไหนสะดวกต่อการ rollback
Best Practices 10 ข้อ
- ใช้ specific tag แทน latest — เช่น
node:20.11-alpineแทนnode:latestเพื่อให้ build reproducible ทุกครั้ง - COPY dependency file ก่อน source — เช่น COPY package.json ก่อน COPY . เพื่อใช้ประโยชน์จาก Docker layer cache ถ้า dependency ไม่เปลี่ยนจะไม่ต้อง install ใหม่
- ใช้ .dockerignore — ป้องกันไม่ให้ .git, node_modules, .env ถูก COPY เข้า build context ช่วยให้ build เร็วขึ้นและปลอดภัยขึ้น
- รัน container ด้วย non-root user — สร้าง user ด้วย adduser และใช้ USER instruction เพื่อลด attack surface
- ใช้ HEALTHCHECK — เพิ่ม HEALTHCHECK instruction เพื่อให้ Docker และ orchestrator ตรวจสอบ health ของ container ได้
- Minimize layer count — รวม RUN command ที่เกี่ยวข้องกันด้วย && เพื่อลดจำนวน layer
- ใช้ alpine หรือ distroless — เลือก base image ที่เล็กที่สุดเท่าที่ application ต้องการ
- อย่าเก็บ secret ใน image — ใช้ Docker BuildKit secret mount (
--mount=type=secret) แทนการ COPY secret file เข้าไป - Set metadata ด้วย LABEL — เพิ่ม LABEL เช่น maintainer, version, description เพื่อให้ manage image ได้ง่าย
- ใช้ multi-platform build — ใช้
docker buildx build --platform linux/amd64, linux/arm64เพื่อรองรับทั้ง x86 และ ARM architecture
เปรียบเทียบ Base Image ยอดนิยม
การเลือก base image สำหรับ final stage มีผลต่อทั้งขนาดและความปลอดภัย:
| Base Image | ขนาด | Package Manager | เหมาะกับ |
|---|---|---|---|
| scratch | 0 MB | ไม่มี | Go, Rust (static binary) |
| alpine:3.19 | 7 MB | apk | ทุกภาษายอดนิยมที่สุด |
| distroless | 2-20 MB | ไม่มี | Java, Python, Node.js |
| debian-slim | 80 MB | apt | Application ที่ต้องการ glibc |
| ubuntu | 78 MB | apt | Development, debugging |
คำแนะนำ: เริ่มจาก alpine หรือ distroless ก่อนถ้า application มีปัญหากับ musl libc (ซึ่ง alpine ใช้) ค่อยเปลี่ยนไปใช้ debian-slim ที่ใช้ glibc แทน
Debugging Multi-stage Build
เมื่อ multi-stage build มีปัญหามีเทคนิคหลายอย่างที่ช่วยแก้ debug ได้:
Build เฉพาะ Stage ที่ต้องการ
# Build แค่ builder stage เพื่อตรวจสอบ
docker build --target builder -t myapp:debug .
# เข้าไป shell เพื่อ debug
docker run -it myapp:debug /bin/sh
ดู Layer และ Size ทีละ Stage
# ดูขนาดแต่ละ layer
docker history myapp:latest
# ใช้ dive เพื่อดูรายละเอียด layer
dive myapp:latest
ตรวจสอบ Build Cache
# ดู build cache usage
docker builder prune --dry-run
# Force rebuild โดยไม่ใช้ cache
docker build --no-cache .
เครื่องมือ dive เป็น open source tool ที่แสดง layer ของ image เป็น interactive UI ช่วยให้เห็นว่าแต่ละ layer เพิ่มไฟล์อะไรบ้างและมีไฟล์ไหนที่ไม่จำเป็น
Multi-stage build ช้ากว่า single-stage build หรือไม่?
ไม่จำเป็นใน build แรกอาจใช้เวลาพอๆกันแต่ build ครั้งถัดไปจะเร็วกว่ามากเพราะ Docker cache แต่ละ stage แยกกันถ้า dependency ไม่เปลี่ยน stage ที่ install dependency จะถูก cache ไว้ทั้งหมดและ image ที่เล็กกว่าจะ push/pull เร็วกว่าหลายเท่า
ใช้ multi-stage กับ Docker Compose ได้หรือไม่?
ได้ครับ Docker Compose จะ build ตาม Dockerfile ปกติคุณสามารถระบุ target stage ใน docker-compose.yml ได้ด้วยเช่น build: { context: ., target: production } ซึ่งมีประโยชน์มากเมื่อต้องการ build stage ที่ต่างกันสำหรับ development กับ production
มี stage สูงสุดได้กี่ stage?
Docker ไม่ได้จำกัดจำนวน stage แต่ในทางปฏิบัติมักใช้ 2-4 stage เช่น dependency stage, test stage, build stage, production stage การมี stage มากเกินไปจะทำให้ Dockerfile อ่านยากและ maintain ลำบาก
COPY --from สามารถ copy จาก external image ได้หรือไม่?
ได้ครับคุณสามารถ COPY จาก image ใดก็ได้เช่น COPY --from=nginx:alpine /etc/nginx/nginx.conf /etc/nginx/nginx.conf ไม่จำเป็นต้องเป็น stage ใน Dockerfile เดียวกันวิธีนี้มีประโยชน์เมื่อต้องการดึง config หรือ binary จาก official image มาใช้
ควรใช้ alpine หรือ distroless สำหรับ production?
ทั้งสองเป็นตัวเลือกที่ดี Alpine มีขนาดเล็กมาก (7 MB) และมี shell กับ package manager ทำให้ debug ง่ายแต่ใช้ musl libc ที่อาจมีปัญหากับบาง library ส่วน distroless ไม่มี shell เลยปลอดภัยกว่าแต่ debug ยากกว่าสำหรับมือใหม่แนะนำ alpine เพราะใช้งานง่ายกว่า
สรุป
Docker Multi-stage Build เป็นเทคนิคที่ ทุก DevOps engineer ต้องรู้ ช่วยลดขนาด image ได้ 70-99% เพิ่มความปลอดภัยลดเวลา deploy และทำให้ CI/CD pipeline มีประสิทธิภาพมากขึ้นเริ่มต้นจากการแยก build stage กับ production stage เป็น 2 FROM statement แล้วค่อยเพิ่มเทคนิค advanced ตามที่ต้องการ
อ่านเพิ่มเติม: บทความทั้งหมด | หน้าแรก Blog
อ่านเพิ่มเติม: สอนเทรด Forex | XM Signal | IT Hardware | อาชีพ IT