ข้ามไปยังเนื้อหา

Base Image ขนาดเล็กและ .dockerignore

เมื่อรัน docker build . Docker จะ package ทุกอย่างในไดเรกทอรีปัจจุบันเป็น build context และส่งไปยัง build daemon Build context ขนาดใหญ่จะทำให้ทุก build ช้าลง แม้ว่าไฟล์ส่วนใหญ่จะไม่ถูกใช้โดย instruction COPY ใดๆ

Sending build context to Docker daemon 823.4MB ← node_modules รวมอยู่ด้วย!

ไฟล์ .dockerignore บอก Docker ว่า path ใดบ้างที่ควรยกเว้นจาก build context — เหมือนกับที่ .gitignore ทำสำหรับ Git

สร้าง .dockerignore ในไดเรกทอรีเดียวกับ Dockerfile:

# Dependencies — ติดตั้งใหม่ภายใน image
node_modules/
vendor/
# Version control
.git/
.gitignore
# Editor และ OS noise
.DS_Store
*.log
.env
.env.*
# Test และ CI files ที่ไม่จำเป็นใน runtime
__tests__/
coverage/
.github/
*.test.ts
*.spec.ts

หลังเพิ่มไฟล์นี้ build context จะลดจากหลายร้อย MB เหลือแค่ไม่กี่ KB สำหรับโปรเจกต์ Node.js ทั่วไป นอกจากนี้การยกเว้นไฟล์ .env ยังป้องกันไม่ให้ secret รั่วไหลเข้าไปใน image โดยไม่ตั้งใจ

Base image มักเป็นส่วนที่ใหญ่ที่สุดของขนาด image ต่อไปนี้คือตัวเลือกทั่วไปจากใหญ่สุดไปเล็กสุด:

Baseขนาดโดยประมาณหมายเหตุ
ubuntu:24.04~78 MBapt ecosystem เต็มรูปแบบ; ใหญ่
debian:bookworm~117 MBDebian เต็ม; พบบ่อย
node:22~1.1 GBรวม Debian เต็มและ devtools
node:22-slim~235 MBDebian slim; ไม่มี devtools
node:22-alpine~55 MBAlpine Linux; เล็กที่สุดที่มี shell
cgr.dev/chainguard/node~50 MBDistroless; minimal, ไม่มี shell
scratch0 MBว่างเปล่าสมบูรณ์; สำหรับ static binary

Alpine ใช้ musl libc และ BusyBox เป็น base image ขนาดเล็กที่ได้รับความนิยมมากที่สุดสำหรับ container เพราะ:

  • Base image ขนาดต่ำกว่า 5 MB
  • Package manager apk ครอบคลุม tool ทั่วไปส่วนใหญ่
  • Official image สำหรับ Node.js, Python, Go, Ruby และอื่นๆ ล้วนมี variant -alpine
FROM node:22-alpine

ข้อควรระวัง: Alpine ใช้ musl แทน glibc package บางอย่างที่ compile สำหรับ glibc (เช่น Node addon บางตัว) อาจต้องการงานเพิ่มเติม สำหรับ application pure-JS มักจะใช้แทนกันได้โดยตรง

Distroless images ของ Google มีเฉพาะ language runtime เท่านั้น — ไม่มี shell, ไม่มี package manager, ไม่มี ls หรือ cat ทำให้เล็กกว่าและมี attack surface น้อยกว่ามากเพราะไม่มี tooling ที่ผู้โจมตีจะใช้ประโยชน์ได้

FROM gcr.io/distroless/nodejs22-debian12

เพราะไม่มี shell, CMD และ ENTRYPOINT ต้องใช้ exec form (JSON array):

CMD ["dist/server.js"]

เมื่อต้องติดตั้ง package ด้วย apt-get ควร cleanup package list ใน RUN layer เดียวกันเสมอ:

RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*

--no-install-recommends ข้าม optional recommended package rm -rf /var/lib/apt/lists/* ลบ package index ที่ดาวน์โหลดออกจาก layer หากวางไว้ใน RUN instruction แยกต่างหาก ขนาดนั้นถูก commit ไปแล้วและการ cleanup จะไม่มีผลใดๆ

snippet ด้านล่างแสดง pattern .dockerignore และ Alpine base image

# syntax=docker/dockerfile:1

# .dockerignore (create alongside this Dockerfile):
# node_modules/
# .git/
# .env
# coverage/

FROM node:22-alpine
WORKDIR /app

# Only manifests first for cache efficiency
COPY package.json ./
RUN echo '{}' > package-lock.json

# Application source (node_modules excluded by .dockerignore)
COPY src/ ./src/ 2>/dev/null || true
RUN echo "console.log('Running on Alpine, image is tiny!');" > src/index.js 2>/dev/null || true

ENV NODE_ENV=production
EXPOSE 3000
CMD ["node","src/index.js"]

# --- Build and check the size ---
# docker build -t myapp:alpine .
# docker images myapp:alpine
ตัวเลือกBenefitCost
เขียน .dockerignore ครอบคลุมทุก path ที่ไม่จำเป็นbuild context เล็กและส่งเร็ว ลดความเสี่ยง secret รั่วไหลเข้า imageต้องดูแลรายการให้ตรงกับโครงสร้างโปรเจกต์ที่เปลี่ยนไปเรื่อยๆ
เลือก base image ที่เล็กกว่า (alpine แทน full distro)ลดขนาด final image ได้หลักร้อย MBnative addon บางตัวที่ compile กับ glibc อาจต้อง build ใหม่สำหรับ musl
  • ไม่มี .dockerignore เลย ทำให้ .git/ และ node_modules/ ถูกส่งเข้า build context ทุกครั้ง ทำให้ build ช้าลงมากโดยเฉพาะในโปรเจกต์ที่มี history ยาว
  • ลืมยกเว้น .env ใน .dockerignore ทำให้ secret ในไฟล์ local หลุดเข้าไปใน image โดยไม่ตั้งใจ
  • เลือก base image ใหญ่อย่าง node:22 แทน node:22-alpine ทั้งที่ application ไม่ได้ต้องการ tool เพิ่มเติมใดๆ ในช่วง runtime

💡 ตัวอย่างจากของจริง

เอกสาร best practice อย่างเป็นทางการของ Docker เตือนซ้ำว่าโปรเจกต์ Node.js ที่ไม่มี .dockerignore มักส่ง build context หลายร้อย MB เพราะ node_modules/ ถูกรวมไปด้วยทุกครั้ง ทั้งที่ dependency จะถูกติดตั้งใหม่ภายใน image อยู่แล้ว

จุดประสงค์หลักของไฟล์ .dockerignore คืออะไร?
ทำไม `rm -rf /var/lib/apt/lists/*` ต้องอยู่ใน RUN instruction เดียวกับ `apt-get install`?
base image ใดต่อไปนี้เล็กที่สุดและใช้กันมากที่สุดสำหรับ Node.js production image?
ข้อจำกัดสำคัญของ distroless image คืออะไร?