เกิดอะไรขึ้น
เราพบไฟล์ .env อยู่ใน image สุดท้ายซ้ำ ๆ ทั้งที่เชื่อว่า .dockerignore กันไว้แล้ว น่ากังวลมาก เพราะ image นั้นกำลังจะถูก push ขึ้น registry
ไฟล์นี้หลุดมาจาก stage หลังที่ copy ทั้งโฟลเดอร์ออกมาจาก stage ก่อนหน้า กฎ ignore ของเราเลยไม่เคยถูกใช้ตรงจุดนั้น
ทำไมถึงเป็นแบบนั้น
.dockerignore กรองแค่ build context คือไฟล์ที่ client ส่งให้ builder สำหรับคำสั่งอย่าง COPY . . และ ADD มันไม่ได้อธิบาย filesystem ของแต่ละ stage COPY --from=<stage> อ่านจาก stage หรือ image นั้นโดยตรง ไฟล์ ignore เลยไม่เกี่ยวเลย
แปลว่าอะไรก็ตามที่เข้าไปอยู่ใน stage ก่อนหน้าได้ ก็ถูกส่งต่อได้ ไฟล์อาจเข้าไปเพราะ pattern ไม่ตรง (.env เฉย ๆ ตรงเฉพาะที่ root ของ context ถ้าอยู่ในโฟลเดอร์ย่อยต้องใช้ **/.env) เพราะ step RUN สร้างมันขึ้นมา หรือเพราะมีอยู่ใน base image อยู่แล้ว พอ copy ทั้งโฟลเดอร์ก็พาไปหมดเลย
ตอนนี้เราทำอย่างไร
ตอนนี้ใน stage หลัง ๆ เรา copy เป็น path เจาะจง เช่น โฟลเดอร์ build output กับ package manifest และไม่ copy ทั้งโฟลเดอร์แอปอีก เรายังปรับ .dockerignore ให้ใช้ **/.env* เพื่อครอบคลุมไฟล์ในโฟลเดอร์ย่อย แต่ถือว่าเป็นแค่แนวป้องกันด่านแรก ไม่ใช่ด่านเดียว
สุดท้ายเราเปิดดู image ที่ได้ การ list ไฟล์ใน image สุดท้ายใช้เวลาไม่กี่วินาที และบอกได้เลยว่ามีอะไรหลุดมาไหม ส่วน secret ที่ต้องใช้ตอน build ให้ส่งผ่าน BuildKit secret mount แทนการ copy เข้า stage ใด ๆ
FROM node:20-slim
WORKDIR /app
# Explicit paths only. Not: COPY --from=build /app .
COPY --from=build /app/dist ./dist
COPY --from=build /app/package*.json ./
COPY --from=build /app/node_modules ./node_modules
# Verify afterwards:
# docker run --rm --entrypoint ls my-image -la /appเช็กลิสต์สั้น ๆ
- 1เขียนรายการ path ที่ runtime ต้องใช้จริง แล้ว copy เฉพาะตัวนั้น
- 2อย่าใช้ COPY --from=build /app . ใน stage สุดท้าย
- 3ใช้ **/.env* ใน .dockerignore เพื่อกันไฟล์ env ในโฟลเดอร์ย่อยด้วย
- 4รัน image ที่ build เสร็จแล้ว list /app เพื่อเช็กว่าไม่มี secret
จำไว้สั้น ๆ
ให้ถือว่า .dockerignore กรองได้แค่ context และต้องระบุทุก path ที่ copy ข้าม stage ให้ชัดเจน