Optimization & Security : การเขียน Dockerfile ของผมเอง by AP.xyz

AP.xyz
แชร์ :
  • ผู้เขียนใช้ Nuxt3 + Bun.js บน Alpine Linux เป็น Stack หลัก
  • เริ่มจากการจัดการ  .dockerignore เพื่อไม่ให้ไฟล์ไม่จำเป็นหรือข้อมูลสำคัญถูกนำเข้าไปใน Container
  • ใช้ Multi-stage build เพื่อแยกขั้นตอนการ Build และลดขนาดของ Image
  • ขั้นตอนสุดท้ายใช้ Scratch image แทน Distroless เพื่อควบคุมสิ่งที่ถูก Copy เข้าไปอย่างละเอียด
  • มีการจัดการ User/Group ภายใน Container เพื่อเพิ่มความปลอดภัย
  • คัดลอกเฉพาะ Library และ Executable ที่จำเป็น เช่น Bun , libstd++ , libgcc_s และ ld-musl
  • เหตุผลที่ไม่ใช้ Distroless เพราะไม่รองรับ musl libc ซึ่ง Alpine ใช
  • ผู้เขียนเน้นว่าแนวทางนี้อาจไม่ดีที่สุด แต่เป็นสิ่งที่เขาเรียนรู้และปรับปรุงมาเอง

---


สวัสดีครับ ผมได้กลับมาเขียน Blog อีกครั้งแล้วนะครับ หลังจากได้ไปท่องโลกสนธยา โลก DevOps มา ไม่ได้มีเวลากลับมาเขียน Blog เลย คิดถึงผู้อ่านทุกท่านนะครับ และผมหวังอย่างยิ่งว่า บทความนี้ อาจจะเป็นประโยชน์ต่อผู้อ่าน หรือ ผู้ที่ต้องการศึกษา Dockerfile ครับ

Dockerfile ที่ดี ต้องมีอะไรบ้าง หลายคนคงทราบอยู่แล้วนะครับว่า Dockerfile ที่ดี ต้องรู้จักการ Copy ของที่จำเป็นต่อการรันแอปพลิเคชั่น และ ไม่ขาดสิ่งที่จำเป็นไป โดยในการทำ Dockerfile นั้นสิ่งที่ต้องห้ามเลย คือ Copy Credentials / Environment หรือ Sensitive Object ต่างๆนะครับ เอาขึ้นไป ผมขอตีมือทีนึงนะ 5555555555

เอาจริงๆ ผมเองก็มาเริ่ม Nerd กับ Dockerfile ในช่วงหลังๆชีวิตการทำงานตลอด 4 ปีที่ผ่านมานะครับ สมัยก่อน มีอะไรก็ก๊อปๆใส่ไปก่อน เอาขึ้นให้ได้ ช่วงต่อมา ก็เริ่มรู้แล้วว่า อะไรที่ไม่ควร Copy ใส่ และ มาถึง Step Build Multi-stage และ ก็มาถึงอีกสเต็ป คือ การเอา Dockerfile ยัดเขา Distroless ยัน Scratch Dockerfile ครับ

ในบทความนี้ผมจะมาพาทำ Dockerfile แบบเบาสบาย ปลอดภัย และ คลีน ฉบับผมเองนะครับ โดยผมจะเริ่มจากการที่ผมนำโปรเจกต์ Website ส่วนตัวของผม มาเป็นต้บแบบในบทความนี้นะครับ

Stack ที่ผมนำไป Containerized มีดังนี้ครับ

Code base : Nuxt3 (Vue 3) + Bun.js

OS base : Alpine Linux

มาเริ่มกันเลยดีกว่าครับ ผมจะพาทัวร์ โดยเริ่มจากการดูโครงสร้าง Folder ก่อน

blog9-1.webp

Structure ของ Nuxt3

ทุกคนสังเกตเห็น .dockerignore ไหมครับ อันนี้แหละครับที่เป็นตัวช่วยทุกท่านในการ ไม่เอา File ที่เราไม่ต้องการ รวมถึง Sensitive Object ขึ้นไป เวลา Build Dockerfile ครับ

blog9-2.webp

.dockerignore

ในไฟล์นี้ผมจะไม่เอาสิ่งที่เป็น Output เวลาที่ผม Build บน Local ขึ้นไป หรือ ไฟล์ต่างๆที่ไม่จำเป็น หรือ เกินจำเป็น เวลา Build ครับ เช่น node_modules, .output, .nuxt, .git หรือว่าพวก Sensitive Object เช่น .env หรือสิ่งที่ Editor เป็นคน Generate ให้ เช่น .vscode, .idea หรือ อื่นๆครับ และ ถ้า เป็นพวก Script ต่างๆที่ใช้งานเกี่ยวกับ Deployment ผมก็ไม่เอาขึ้นไปเช่นกันครับ เช่น ที่เก็บ File Helm Chart , YAML ของ K8s

มาถึงส่วนที่สำคัญกันบ้างล่ะครับ คือ Dockerfile

FROM oven/bun:alpine as base # Choose the distro we’ll use as the build base

# Copy only what’s needed for dependency install
COPY package.json bun.lock ./app/

WORKDIR /app # Set the working directory

RUN bun install --frozen-lockfile # Install deps for building Nuxt

# ---------- Build stage ----------
FROM base AS prerelease

# Create a dedicated, non-root user
ENV USER_NAME=isaac
ENV UID=1001
ENV GID=1001

# Install minimal packages and create user/group
RUN apk update && \
apk add --no-cache ca-certificates && \
addgroup -g ${GID} ${USER_NAME} && \ # Create group
adduser -D -u ${UID} -G ${USER_NAME} ${USER_NAME} && \ # Create user and add to group
chown -R ${USER_NAME}:${USER_NAME} /app # Own /app

USER ${USER_NAME} # Drop privileges

# Reuse node_modules from the base stage
COPY --from=base /app/node_modules node_modules
# Copy the rest of the app (excluding items filtered by .dockerignore)
COPY . .

ENV NODE_ENV=production # Build in production mode

RUN bun run build # Build Nuxt (.output)

# ---------- Final minimal runtime ----------
FROM scratch # Ultra-minimal image with only what we copy in

# Copy required user/group files so the non-root user works at runtime
COPY --from=prerelease /etc/passwd /etc/passwd
COPY --from=prerelease /etc/group /etc/group

ENV USER_NAME=isaac
USER ${USER_NAME}

WORKDIR /app

# Copy Bun runtime and essential runtime libs
COPY --from=base /usr/local/bin/bun /usr/local/bin/bun
COPY --from=base "/usr/lib/libstdc++.so.6" "/usr/lib/libstdc++.so.6"
COPY --from=base /usr/lib/libgcc_s.so.1 /usr/lib/libgcc_s.so.1
# Dynamic linker (must match architecture):
# - x86_64: /lib/ld-musl-x86_64.so.1
# - arm64: /lib/ld-musl-aarch64.so.1
COPY --from=base /lib/ld-musl-x86_64.so.1 /lib/ld-musl-x86_64.so.1

# Copy built output (ensure ownership matches the runtime user)
COPY --from=prerelease --chown=${USER_NAME}:${USER_NAME} /app/.output /app

ENV NUXT_HOST=0.0.0.0
ENV NUXT_PORT=3000

EXPOSE 300

CMD ["bun", "run", "server/index.mjs"]

ที่ท่านเห็นอยู่ข้างบนนี้คือ Dockerfile ของโปรเจกต์ ผมเองนะครับ

แต่ละอัน ผมจะคอมเมนต์ และ อธิบายไว้ในไฟล์นะครับ


สังเกตไหมครับว่า Dockerfile ดังกล่าว ผมใช้ scratch สาเหตุที่ผมใช้มีอยู่อย่างเดียวเลย …. ป้องกัน Shell Execution ครับ


ค่อนข้างจะละเอียดเลยครับ เป็นไฟล์ที่ผมใช้เวลาศึกษาพอสมควร กว่าจะได้ Dockerfile นี้มา อาจจะรู้สึกว่ามันยุ่บยั่บไปหน่อย ไหนจะเรื่อง Lib ต่างๆที่ Copy มาเอง ไหนจะเป็น ld ที่จะต้องมาเลือก Architecture ภายหลัง วุ่นวายดีครับ

แต่ผมจะบอกอย่างนึงนะครับ ที่ผมเลือกใช้ scratch แทน distroless เพราะ มันค่อนข้างเห็นชัดดีครับว่า อะไรที่เราจำเป็นต้อง Copy บ้าง และ distroless อาจจะไม่ตอบโจทย์ผมในเรื่องของการใช้งาน Alpine linux ครับ เพราะมันเป็น glibc แต่ Alpine Linux เป็น musl

ไม่ว่าอย่างไรก็ดีครับ การเขียน Dockerfile ในแบบฉบับของผมเอง อาจไม่ใช่วิธีทางที่ดีหรือดูน่าชื่นชมสำหรับพี่ๆน้องๆทุกท่านนะครับ แต่ผมอยากมาแชร์วิธีการเขียนในแนวทางของผม ที่ผมได้ศึกษามา และ ได้มองเห็นจุดอ่อน และ ได้แก้จุดอ่อนในแบบฉบับของผมเพียงเท่านั้นเองครับ ผมยินดีรับความคิดเห็นที่สร้างสรรค์จากพี่ๆ ในวงการ Developer / DevOps / Security ทุกท่านอยู่นะครับ หวังว่าพี่ๆจะใจดีกับผมนะครับ

ขอพระเจ้าอวยพรทุกท่านครับ สวัสดีครับ


บทความที่เกี่ยวข้อง

Cursor AI ยกระดับการพัฒนาด้วย AI-Native Code Editor

Cursor AI ยกระดับการพัฒนาด้วย AI-Native Code Editor

ในโลกของการพัฒนาซอฟต์แวร์ที่ขับเคลื่อนด้วยนวัตกรรมอย่างต่อเนื่อง การมาถึงของ AI ได้พลิกโฉมวิธีคิดและวิธีการทำงานของ Software Developer อีกครั้งหนึ่ง หาก GitHub Copilot คือผู้ช่วยที่ชาญฉลาด Cursor AI คือก้าวต่อไปที่ล้ำไปกว่านั้น ด้วยการออกแบบมาตั้งแต่รากฐานให้เป็น AI-native code editor ที่ไม่ได้มีแค่ฟีเจอร์ช่วยเขียนโค้ด แต่ยังเป็นแพลตฟอร์มที่ช่วยให้นักพัฒนาสามารถคิด, วิจัย, และสร้างโค้ดได้อย่างมีประสิทธิภาพสูงสุด บทความนี้จะเจาะลึกถึงหลักการทำงาน, คุณสมบัติเด่น, และบทบาทของ Cursor AI ที่มีต่ออนาคตของการพัฒนาซอฟต์แวร์

อ่านเพิ่มเติม