Docker Multi-Stage Builds
ปัญหา node_modules ใน Docker
หัวข้อที่มีชื่อว่า “ปัญหา node_modules ใน Docker”เมื่อคุณ containerise แอปพลิเคชัน Node/TypeScript image สุดท้ายต้องมี:
- Node.js runtime เต็มรูปแบบ (100+ MB)
node_modules/(สำหรับ NestJS app ทั่วไปอาจถึง 300–600 MB)- output
.jsที่คอมไพล์แล้ว
ผลลัพธ์มักเป็น image ขนาด 500 MB–1 GB ที่ขน development dependency tree ทั้งก้อนขึ้น production คุณลดขนาดได้ด้วย .dockerignore, --omit=dev และ multi-stage builds แต่ตัว Node runtime เองไม่มีทางหายไป
Go คอมไพล์ออกมาเป็น binary เดียวแบบ statically-linked ที่ไม่ต้องพึ่ง runtime ใด ๆ image สุดท้ายจึง build จาก FROM scratch — base image ที่ว่างเปล่าจริง ๆ — โดยมีแค่ binary ของคุณอยู่ข้างในได้เลย ขนาด production image ของ Go จึงอยู่ราว 5–20 MB
Dockerfile multi-stage สำหรับ Go
หัวข้อที่มีชื่อว่า “Dockerfile multi-stage สำหรับ Go”Multi-stage build ใช้ Go builder image เต็มรูปแบบในการคอมไพล์ แล้ว copy เฉพาะ binary ที่ได้ไปยัง final image ขนาดเล็ก
# ---- Stage 1: Build ----FROM golang:1.22-alpine AS builder
WORKDIR /app
# Copy dependency manifests ก่อนเพื่อ layer cachingCOPY go.mod go.sum ./RUN go mod download
# Copy source และ build static binaryCOPY . .RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server
# ---- Stage 2: Run ----FROM gcr.io/distroless/static-debian12
WORKDIR /app
# Copy เฉพาะ binary จาก builder stageCOPY --from=builder /app/server .
EXPOSE 8080
ENTRYPOINT ["/app/server"]CGO_ENABLED=0 ปิดการใช้ C bindings เพื่อให้ binary เป็น self-contained อย่างสมบูรณ์ -ldflags="-s -w" strip debug symbols ลดขนาด binary ประมาณ ~30% distroless/static คือ minimal image ที่ Google ดูแลซึ่งมีเฉพาะ CA certificates และ timezone data — มีประโยชน์สำหรับ HTTPS calls — แต่ไม่มี shell, ไม่มี package manager, ไม่มี attack surface
ถ้าต้องการ shell สำหรับ debug ให้เปลี่ยน distroless/static-debian12 เป็น alpine:3.20 (ยังแค่ ~7 MB) และเพิ่ม apk add --no-cache ca-certificates
Dockerfile multi-stage สำหรับ Node (เพื่อเปรียบเทียบ)
หัวข้อที่มีชื่อว่า “Dockerfile multi-stage สำหรับ Node (เพื่อเปรียบเทียบ)”# ---- Stage 1: Build ----FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./RUN npm ci
COPY . .RUN npm run build # tsc output → dist/
# Prune dev dependenciesRUN npm prune --omit=dev
# ---- Stage 2: Run ----FROM node:20-alpine
WORKDIR /app
# ต้อง copy node_modules — runtime ยังต้องการมันCOPY --from=builder /app/node_modules ./node_modulesCOPY --from=builder /app/dist ./distCOPY --from=builder /app/package.json .
EXPOSE 3000
CMD ["node", "dist/main.js"]แม้หลัง prune dev deps แล้ว Node runtime และ node_modules ที่เหลือทำให้ image ยังมีขนาด 150–300 MB
# ขนาด Node image สุดท้าย: ~150-300 MB# มี: node runtime + node_modules + dist/
FROM node:20-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ciCOPY . .RUN npm run buildRUN npm prune --omit=dev
FROM node:20-alpineWORKDIR /appCOPY --from=builder /app/node_modules ./node_modulesCOPY --from=builder /app/dist ./distEXPOSE 3000CMD ["node", "dist/main.js"]# ขนาด Go image สุดท้าย: ~5-20 MB# มี: binary เท่านั้น (ไม่มี runtime)
FROM golang:1.22-alpine AS builderWORKDIR /appCOPY go.mod go.sum ./RUN go mod downloadCOPY . .RUN CGO_ENABLED=0 GOOS=linux \ go build -ldflags="-s -w" \ -o /app/server ./cmd/server
FROM gcr.io/distroless/static-debian12WORKDIR /appCOPY --from=builder /app/server .EXPOSE 8080ENTRYPOINT ["/app/server"]เหล่านี้คือตัวอย่าง Dockerfile — รันใน terminal ด้วย
docker build -t myapp .แล้วใช้docker images myappเพื่อเปรียบเทียบขนาด ไม่มี browser playground สำหรับส่วนนี้