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

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 ถูกก็ตาม

ยกฐานข้อมูลขึ้นมา:

Terminal window
cd devblog/infra
docker compose up -d mongo
Terminal window
docker compose ps
# NAME IMAGE STATUS
# infra-mongo-1 mongo:7 Up (healthy)

รอสักครู่ให้ start_period ผ่านไปถ้ายังขึ้น (health: starting) จากนั้นยืนยันว่ายืนยันตัวตนด้วย root user ได้:

Terminal window
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 →