กลับหน้าหลัก
ภาพปกบทความ เจาะ Dockerfile Templates พร้อมใช้งานจริง 5 Stack
DevOps#Docker#Dockerfile#DevOps#Best Practices#Security

เจาะ Dockerfile Templates พร้อมใช้งานจริง 5 Stack

การเขียน Dockerfile ให้ “รันได้” นั้นง่าย แต่การเขียนให้ image เล็ก ปลอดภัย และ build ซ้ำได้เหมือนเดิมทุกครั้ง คือคนละเรื่องกัน บทความนี้ถอดบทเรียนจาก repo inetspa/dockerfile-templates ที่รวบรวม Dockerfile เกรด production ไว้ครบ 5 stack ให้หยิบไปใช้ได้ทันที

NOTE

repo นี้เป็น reference / boilerplate ภายใต้ลิขสิทธิ์ MIT — clone ไปปรับใช้กับโปรเจกต์ของคุณได้เลย แต่ละ stack มีทั้ง Dockerfile, docker-compose.yml, โค้ดตัวอย่าง และ AGENTS.md อธิบายเฉพาะ stack นั้น ๆ


5 Stack ที่ครอบคลุม

#StackBase Image (runtime)PortRuntime
01Python 3.12 + Flaskpython:3.12-slim5000gunicorn
02Python 3.12 + FastAPIpython:3.12-slim8000uvicorn
03Next.js 16node:22-alpine3000node (standalone)
04React 19 (Vite)nginx:1.27-alpine8080nginx
05Go 1.26gcr.io/distroless/static8080static binary

ต่างภาษา ต่าง runtime แต่ทุกตัวยึด หลักการเดียวกัน ซึ่งคือส่วนที่มีค่าที่สุดของ repo นี้


7 หลักการที่ใช้ร่วมกันทุก Stack

  1. Multi-stage build เสมอ — แยก stage “build” (มี toolchain) ออกจาก “runtime” (เหลือเฉพาะของจำเป็น) → image เล็กลงและ attack surface น้อยลง
  2. Layer cache optimization — copy เฉพาะ manifest (requirements.txt / package.json / go.mod) ก่อนติดตั้ง dependencies แล้วค่อย copy source ทีหลัง → แก้โค้ดไม่ต้องลง dependencies ใหม่
  3. Non-root user — image สุดท้ายรันด้วย user ธรรมดา (appuser, nextjs, nginx, nonroot) ไม่ใช่ root
  4. Health check — ทุก image มีวิธีเช็คสุขภาพ ไม่ว่าจะผ่าน HEALTHCHECK หรือผ่าน compose
  5. Pin versions — ใช้ tag แบบ major.minor (เช่น python:3.12-slim) ไม่ใช้ :latest ที่เปลี่ยนใต้เท้าได้
  6. Build args + .dockerignoreBUILDKIT_INLINE_CACHE=1, --no-cache-dir, --no-install-recommends และ .dockerignore ทุกโฟลเดอร์
  7. Timezone Asia/Bangkok — ทุก image ตั้ง TZ และติดตั้ง zoneinfo ให้ตรงกัน (วิธีต่างกันตาม base image — ดูหัวข้อด้านล่าง)

TIP

ถ้าจะจำหลักการเดียวจาก repo นี้ ให้จำข้อ 2: copy manifest ก่อน copy โค้ด เป็นเทคนิคที่ทำให้ rebuild เร็วขึ้นหลายเท่าโดยแทบไม่มีต้นทุน


ไฮไลต์รายตัว

🐍 01–02 · Python (Flask & FastAPI)

ใช้ builder stage ติดตั้ง dependencies แบบ pip install --user แล้ว copy เฉพาะโฟลเดอร์ /root/.local ไป runtime stage — ได้ packages โดยไม่ต้องลากเครื่องมือ build ติดไปด้วย

# builder
RUN pip install --no-cache-dir --user -r requirements.txt
# runtime
COPY --from=builder /root/.local /home/appuser/.local
ENV PATH=/home/appuser/.local/bin:$PATH

Health check ฉลาดตรงที่ใช้ Python stdlib (urllib.request) ไม่ต้องลง curl เพิ่มใน image:

HEALTHCHECK CMD python -c "import urllib.request,sys; \
sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:5000/health').status==200 else 1)"

Flask รันด้วย gunicorn (มี workers/timeout) ส่วน FastAPI รันด้วย uvicorn พร้อม --proxy-headers รองรับการอยู่หลัง reverse proxy

▲ 03 · Next.js 16 — 3 stages + standalone

แยกเป็น 3 stage (depsbuilderrunner) และใช้ output: "standalone" ของ Next.js ที่ tree-shake node_modules ให้เหลือเฉพาะที่ใช้จริง runtime stage จึง copy แค่ 3 อย่าง:

COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static/ ./.next/static/
COPY --from=builder /app/public/ ./public/
CMD ["node", "server.js"]

IMPORTANT

รายละเอียดเล็ก ๆ ที่คนมักพลาด: base image node:22-alpine มี user node ครอง UID 1000 อยู่แล้ว repo จึงสร้าง user nextjs ด้วย UID 1001 เพื่อเลี่ยงการชนกัน

⚛️ 04 · React (Vite) — build แล้วโยนให้ nginx

Vite build ออกมาเป็น static แล้วเสิร์ฟด้วย nginx:1.27-alpine จุดน่าสนใจคือใช้ port 8080 (unprivileged) แทน 80 เพื่อให้ nginx รันเป็น non-root user ได้:

# nginx user (UID 101) เปิด port < 1024 ไม่ได้ จึงใช้ 8080
EXPOSE 8080
# run: docker run -p 8080:8080 react-demo

🐹 05 · Go — distroless ที่เล็กและปลอดภัยที่สุด

compile เป็น static binary (CGO_ENABLED=0) แล้ววางบน gcr.io/distroless/static-debian12:nonroot ที่ ไม่มี shell, ไม่มี package manager เหลือ attack surface น้อยที่สุด

RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -trimpath -o /out/server .
# runtime: distroless nonroot (UID 65532) — ไม่ต้องสร้าง user เอง

มีไฟล์ Dockerfile.alpine เป็นทางเลือกสำหรับคนที่อยากได้ shell + HEALTHCHECK แบบ wget


เกร็ดเด็ด: จัดการ Timezone ตาม Base Image

นี่คือจุดที่ repo ทำได้ละเอียดมาก เพราะแต่ละ base image จัดการ zoneinfo ไม่เหมือนกัน:

Base Imageวิธีตั้ง Asia/Bangkok
debian-slim (Flask/FastAPI)apt-get install tzdata แล้ว symlink /etc/localtime
alpine (Next.js/React)apk add --no-cache tzdata แล้ว symlink
distroless (Go)ไม่มี zoneinfo ติดมา → COPY เฉพาะไฟล์ Asia/Bangkok จาก builder stage

WARNING

distroless ไม่มี shell จึงใช้ HEALTHCHECK แบบ shell command ไม่ได้ — repo แก้ด้วยการย้าย healthcheck ไปไว้ใน docker-compose.yml แทน (เรียก binary ด้วย flag -healthcheck)


เริ่มใช้งานอย่างรวดเร็ว

git clone https://github.com/inetspa/dockerfile-templates
cd dockerfile-templates

# ตัวอย่าง: Python + Flask
cd 01-python-flask
docker build -t flask-demo .
docker run --rm -p 5000:5000 flask-demo

# ตัวอย่าง: Go (distroless)
cd ../05-go
docker build -t go-demo .
docker run --rm -p 8080:8080 go-demo

แต่ละโฟลเดอร์มี docker compose up --build ให้ใช้สำหรับ dev ได้เช่นกัน


💡 สรุปสิ่งที่ถอดมาใช้ได้ทันที

  1. Multi-stage + copy manifest ก่อน — image เล็กลงและ build เร็วขึ้น
  2. Non-root ทุก image — ลดความเสี่ยงเมื่อถูกเจาะ
  3. เลือก base ให้เหมาะกับงานslim สำหรับ Python, alpine สำหรับ static serve, distroless เมื่อต้องการความปลอดภัยสูงสุด
  4. อย่าลืม timezone & health check — รายละเอียดเล็ก ๆ ที่แยก “ใช้ได้” กับ “ใช้ได้จริงบน production”

อยากเข้าใจลึกขึ้น แนะนำให้อ่าน AGENTS.md ในแต่ละโฟลเดอร์ของ repo ครับ — เขียนอธิบายเหตุผลเบื้องหลังทุกบรรทัดไว้อย่างดี


📚 แหล่งอ้างอิง (Source)

บทความนี้เรียบเรียงจาก source code และเอกสารใน repository ต่อไปนี้:

  • Repository: inetspa/dockerfile-templates
  • เจ้าของ: INET (inetspa)
  • License: MIT
  • ไฟล์ที่อ้างอิง: README.md, AGENTS.md (root + รายโฟลเดอร์), และ Dockerfile ของทั้ง 5 stack (01-python-flask, 02-python-fastapi, 03-nextjs, 04-react, 05-go)

NOTE

โค้ดตัวอย่างทั้งหมดในบทความนี้คัดมาจาก repo ต้นทางโดยตรง ลิขสิทธิ์เป็นของผู้พัฒนาเดิมภายใต้สัญญาอนุญาต MIT