Compose Skeleton
สิ่งที่จะสร้าง
หัวข้อที่มีชื่อว่า “สิ่งที่จะสร้าง”infra/docker-compose.yml ที่ตอนนี้รันเซอร์วิสเดียวคือ mongo พร้อม root credential ชุดที่ทั้ง MONGODB_URI และการเชื่อมต่อ Mongoose ในภายหลังคาดไว้ บวก named volume ให้ข้อมูลรอดแม้คอนเทนเนอร์รีสตาร์ต และ healthcheck
API กับเว็บแอปยังไม่ต่อเข้ากับ Compose ตอนนี้ทั้งคู่ยังรันในเครื่องผ่าน npm run start:dev และ npm run dev แล้วเชื่อมไปที่ MongoDB ตัวไหนก็ตามที่เข้าถึงได้ การยก Mongo ขึ้นมาก่อนตัวเดียวทำให้คุณตรวจฐานข้อมูลแบบแยกเดี่ยวได้ด้วย docker compose ps และ mongosh ตั้งแต่ยังไม่มีโค้ดแอปตัวไหนพึ่งพาอยู่ ส่วน apps/api และ apps/web จะเข้ามาอยู่ในไฟล์นี้ตอนโมดูล 12
ข้อดีข้อเสีย
หัวข้อที่มีชื่อว่า “ข้อดีข้อเสีย”ไฟล์ compose บางส่วนตอนนี้ เทียบกับสแตกเต็มตั้งแต่วันแรก ถ้าเพิ่มเซอร์วิส api และ web เข้าไปเลย ทุกครั้งที่แก้โค้ดจะต้องวน rebuild แล้ว restart ผ่าน Docker ซึ่งช้ากว่า hot reload ของ npm run start:dev มาก แถม ณ จุดนี้ของคอร์สก็ยังไม่มีอะไรให้ containerize อยู่ดี การเริ่มด้วย mongo ตัวเดียวจึงได้ฐานข้อมูลที่เสถียรและทิ้งได้ โดยไม่เสียความเร็วในการวนรอบพัฒนาโค้ดในเครื่องเลย
named volume เทียบกับ bind mount สำหรับข้อมูลของ Mongo named volume (mongodata) ให้ Docker จัดการเองทั้งหมด ข้อมูลรอดหลัง docker compose down (ที่ไม่ใส่ -v) ไฟล์ฐานข้อมูลไม่กระจายเข้ามาใน working tree ของคุณ และเลี่ยงปัญหาสิทธิ์ไฟล์กับการแยกตัวพิมพ์เล็กใหญ่ที่ bind mount ชอบเจอบน macOS ส่วน bind mount (./data:/data/db) ให้คุณเข้าถึงไฟล์ดิบบน host ตรง ๆ ซึ่งบางทีก็มีประโยชน์ตอนตรวจด้วยมือ แต่แลกกับการผูกคอนเทนเนอร์ไว้กับ path บน host บทเรียนนี้เลือก named volume
ติดตั้ง
หัวข้อที่มีชื่อว่า “ติดตั้ง”สร้าง infra/docker-compose.yml:
services: mongo: image: mongo:7 environment: MONGO_INITDB_ROOT_USERNAME: devblog MONGO_INITDB_ROOT_PASSWORD: devblog ports: - "27017:27017" volumes: - mongodata:/data/db healthcheck: test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] interval: 10s timeout: 5s retries: 5 start_period: 10s
volumes: mongodata: {}MONGO_INITDB_ROOT_USERNAME/MONGO_INITDB_ROOT_PASSWORD คือตัวแปร bootstrap ของ image Mongo ทางการ ตอนสตาร์ตครั้งแรกด้วย data directory ว่างเปล่า entrypoint script จะสร้าง root user จาก credential คู่นี้ไว้ในฐานข้อมูลพิเศษชื่อ admin นี่คือเหตุผลที่ MONGODB_URI ใน .env.example ลงท้ายด้วย ?authSource=admin เพราะต้องบอก Mongoose driver ให้ยืนยันตัวตนกับฐานข้อมูล admin ที่ root user อยู่จริง ไม่ใช่กับฐานข้อมูล devblog ที่อยู่ในส่วน path ก่อนหน้า ถ้าลืมใส่ authSource=admin การเชื่อมต่อจะ fail ด้วย authentication error ทุกครั้ง ต่อให้ username กับ password ถูกก็ตาม
ยกฐานข้อมูลขึ้นมา:
cd devblog/infradocker compose up -d mongoตรวจสอบผล
หัวข้อที่มีชื่อว่า “ตรวจสอบผล”docker compose ps# NAME IMAGE STATUS# infra-mongo-1 mongo:7 Up (healthy)รอสักครู่ให้ start_period ผ่านไปถ้ายังขึ้น (health: starting) จากนั้นยืนยันว่ายืนยันตัวตนด้วย root user ได้:
docker compose exec mongo mongosh -u devblog -p devblog --eval "db.adminCommand('ping')"# { ok: 1 }{ ok: 1 } ยืนยันสามอย่าง: คอนเทนเนอร์ขึ้นแล้ว root user สร้างด้วย credential จาก .env.example เรียบร้อย และเซิร์ฟเวอร์ตอบคำสั่งได้ นี่คือการจับมือแบบเดียวกับที่ MONGODB_URI จะทำตอน apps/api เชื่อมต่อผ่าน Mongoose ในโมดูลถัดไป
infra/docker-compose.yml รัน mongo:7 ตัวเดียว seed root user devblog/devblog ผ่าน MONGO_INITDB_ROOT_USERNAME/MONGO_INITDB_ROOT_PASSWORD เก็บข้อมูลไว้ใน named volume mongodata และรายงานสถานะผ่าน healthcheck ที่เรียก mongosh --eval ทั้ง docker compose ps และ ping ที่ยืนยันตัวตนแล้วต่างยืนยันว่าฐานข้อมูลพร้อมใช้ ตัว API กับเว็บแอปจะเชื่อมมาที่ instance นี้ในเครื่องตลอดคอร์สที่เหลือ แล้วค่อยเข้ามาอยู่ในไฟล์ Compose นี้เองตอนโมดูล 12
พอ scaffold monorepo เสร็จ API บูตที่พอร์ต 4000 เว็บแอปบูตที่พอร์ต 3000 และ MongoDB รันอยู่และเข้าถึงได้ โมดูล 1 ก็จบสมบูรณ์ คุณมี toolchain ในเครื่องพร้อมสำหรับทุกอย่างที่ตามมา
ถัดไป: Data Modeling →