โหมดมืด
บทที่ 04 — Docker Compose: จัดการหลาย Container ด้วยไฟล์เดียว
3 บทที่ผ่านมา เราสั่ง container ด้วย docker run ทีละตัว — พิมพ์ option ยาวเหยียด. บทนี้สอน Docker Compose — เครื่องมือที่ทำให้จัดการ "ระบบที่มีหลาย container" ด้วยไฟล์ config ไฟล์เดียว
อ่านจบจะ:
- เข้าใจว่าทำไม
docker runทีละตัว ไม่ไหวเมื่อระบบโต - เขียน
compose.yamlเป็น — อธิบายได้ทุกบรรทัด - รัน stack ทั้งระบบ (web + api + database + cache) ด้วยคำสั่งเดียว
- ใช้ healthcheck, depends_on, profile, override สำหรับ production
ใช้เวลา 3-4 ชั่วโมง
📚 ไม่ต้องอ่านรวดเดียวจบ — แบ่งอ่านได้ ลองเขียน compose.yaml ตาม Lab ไปด้วยจะเข้าใจกว่าอ่านยาว ๆ รวดเดียว
Part 1: ปัญหาของ docker run
ลองนึกระบบจริง — มี 4 container:
bash
# network
docker network create app-net
# database
docker run -d --name db --network app-net \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=myapp \
-v pgdata:/var/lib/postgresql/data \
postgres:16
# redis cache
docker run -d --name cache --network app-net \
redis:7
# backend api
docker run -d --name api --network app-net \
-e DATABASE_HOST=db \
-e REDIS_HOST=cache \
-e DATABASE_PASSWORD=secret \
myapi:v1
# nginx
docker run -d --name web --network app-net \
-p 80:80 \
-v ./nginx.conf:/etc/nginx/nginx.conf:ro \
nginx:1.25ปัญหา:
- พิมพ์ยาวมาก — จำ option ทุกตัวไม่ไหว
- ลำดับสำคัญ — ต้องรัน db ก่อน api
- แชร์กับทีมยาก — ต้องส่งคำสั่งทั้งหมดให้เพื่อน
- เก็บประวัติยาก — config ไม่ได้อยู่ใน Git
- ลบ/restart ทั้งระบบ — ต้องไล่ทำทีละตัว
→ Docker Compose แก้ทุกข้อ — เขียน config ทั้งหมดเป็นไฟล์ compose.yaml ไฟล์เดียว แล้วสั่ง docker compose up ครั้งเดียวจบ
Part 2: Compose ไฟล์แรก
compose.yaml ที่ทำงานเทียบเท่าคำสั่งยาว ๆ ข้างบน:
ก่อนดู YAML — ลองนึกภาพระบบที่ compose ไฟล์นี้สร้างขึ้น:
yaml
name: myapp # ⭐ project name — pin ไว้ให้คงที่ทุกเครื่อง
# (ถ้าไม่ใส่ Compose จะใช้ชื่อโฟลเดอร์แทน)
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} # ⚠️ อย่าฝัง password ตรง ๆ — ดู Part 9
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
cache:
image: redis:7
api:
image: myapi:v1
environment:
DATABASE_HOST: db
REDIS_HOST: cache
DATABASE_PASSWORD: ${DB_PASSWORD} # ⚠️ อย่าฝัง password ตรง ๆ — ดู Part 9
web:
image: nginx:1.25
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
volumes:
pgdata:แล้วสั่ง:
bash
docker compose up -dเท่านี้ — Docker Compose สร้าง network, สร้าง volume, รัน 4 container ให้ครบ ตามลำดับที่เหมาะสม
💡 Tip: ตัวอย่างข้างบนตัด option หลาย ๆ ตัวออกเพื่อให้อ่านง่าย — ในงานจริงควรเพิ่ม
restart: unless-stoppedให้ container restart อัตโนมัติถ้า crash หรือ server reboot รายละเอียดครบใน Part 5
⚠️
version:field obsolete แล้ว — Compose Specification ใหม่ (ตั้งแต่ Compose v2) ถอดversion:ออก ถ้าใส่จะเจอ warning เวลาสั่งdocker compose upไฟล์เก่า ๆ ที่ขึ้นต้นด้วยversion: "3.8"ลบบรรทัดนั้นทิ้งได้เลย — ไม่ต้องแทนที่ด้วยอะไร📝 Compose v2 vs v1: Compose v2 = เวอร์ชันใหม่ที่ติดมากับ Docker โดยตรง (สั่งด้วย
docker composeมี space) ส่วน v1 = เวอร์ชันเก่าที่ต้องลงแยก (docker-composeมีขีด) — v1 EOL แล้วตั้งแต่ปี 2023💡
name:top-level = pin project name ให้คงที่ ไม่ขึ้นกับชื่อโฟลเดอร์ (ถ้าไม่ใส่ Compose ใช้ชื่อโฟลเดอร์ปัจจุบันเป็น project name — เปลี่ยน path แล้วเจอ container/volume คนละชุดได้)
2.1 ทำไม Compose ไม่ต้องระบุ network
สังเกตว่าใน compose.yaml ข้างบน — เราไม่ได้สร้าง network เลย แต่ container คุยกันได้
เพราะ Compose สร้าง network ให้อัตโนมัติ 1 อัน แล้วใส่ทุก service เข้า network นั้น — และมันเป็น custom bridge → มี DNS → service เรียกชื่อกันได้
นั่นคือเหตุผลที่ api ตั้ง DATABASE_HOST: db ได้ — db คือ "ชื่อ service" ใน compose ซึ่งกลายเป็นชื่อ DNS ที่ใช้เรียกได้
Part 3: รู้จัก YAML ก่อน (สำหรับมือใหม่)
compose.yaml เขียนด้วยภาษา YAML (อ่านว่า "แยม-เมิล" หรือ "ยา-เมิล" — ย่อจาก "YAML Ain't Markup Language") — มือใหม่ต้องเข้าใจกฎพื้นฐาน
yaml
# comment ขึ้นต้นด้วย #
key: value # คู่ key-value
parent: # nested — ใช้ "การเยื้อง (indent)" บอกระดับ
child: value
child2: value
list: # list — ขึ้นต้นด้วย -
- item1
- item2
key: "value with: colon" # ถ้า value มีอักขระพิเศษ ใส่ quote⚠️ กฎที่ห้ามผิด ของ YAML:
- เยื้องด้วย space เท่านั้น — ห้ามใช้ Tab (Tab ทำให้ error)
- เยื้องต้องสม่ำเสมอ (มัก ใช้ 2 space)
- การเยื้องคือสิ่งที่บอกความสัมพันธ์ "ใครอยู่ใต้ใคร"
💡 แนะนำใช้ VS Code (ฟรี) พร้อม extension "YAML" ของ Red Hat — มันจะแจ้งเตือนทันทีถ้าใช้ Tab แทน space, highlight indent level ให้เห็นชัด และ autocomplete key ของ compose.yaml ได้
ตัวอย่างที่ผิดบ่อย:
yaml
services:
web:
image: nginx # ❌ เยื้อง 3 space ไม่ตรงกับ 2 space ของบรรทัดอื่นPart 4: โครงสร้างของ compose.yaml
ไฟล์ compose มี 3 ส่วนหลัก:
yaml
services: # ← container ต่าง ๆ (ส่วนหลัก)
...
volumes: # ← volume ที่ใช้
...
networks: # ← network ที่ใช้ (ถ้าต้องการกำหนดเอง)
...💡 หมายเหตุชื่อไฟล์: ชื่อมาตรฐานใหม่คือ
compose.yaml— แต่ชื่อเดิมdocker-compose.ymlก็ยังใช้ได้ Compose อ่านทั้งคู่
Part 5: นิยาม Service ทุก Option (อธิบายละเอียด)
มาดู service ตัวหนึ่งแบบเต็ม ๆ พร้อมคำอธิบายทุกบรรทัด:
yaml
services:
api:
# --- เลือก image หรือ build ---
image: myapi:v1 # ใช้ image สำเร็จรูป
# หรือ build จาก Dockerfile:
build:
context: . # โฟลเดอร์ที่มี Dockerfile
dockerfile: Dockerfile # ชื่อไฟล์ (default: Dockerfile)
args: # build arg
VERSION: "1.0"
target: production # multi-stage: build แค่ stage นี้
container_name: my-api # ตั้งชื่อ container (ปกติไม่ต้องตั้ง)
# --- restart policy ---
restart: unless-stopped # restart อัตโนมัติถ้าล่ม
# --- port mapping ---
# 💡 ใส่ quote รอบ port เสมอ — กัน YAML parse "5060:5061" เป็นเลขฐาน 60
ports:
- "8080:3000" # host:container
- "127.0.0.1:9090:9090" # bind localhost เท่านั้น
# --- environment variables ---
environment:
NODE_ENV: production
DATABASE_HOST: db
env_file: # หรือโหลดจากไฟล์
- .env
# --- volumes ---
volumes:
- mydata:/app/data # named volume
- ./config:/app/config:ro # bind mount, read-only
# --- network ---
networks:
- frontend
- backend
# --- depends_on: ลำดับการ start ---
depends_on:
db:
condition: service_healthy # รอจน db healthy ก่อน
# --- healthcheck ---
# ✅ ใช้ wget เป็น default — รองรับ alpine (ซึ่งติดมากับ busybox) โดยไม่ต้องลงเพิ่ม
# ⚠️ ถ้า image ไม่มีทั้ง wget และ curl (เช่น distroless) — healthcheck จะ fail ตลอด
# ทางเลือก:
# - ถ้า image มี curl: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
# - ติดตั้ง curl ใน Dockerfile: `RUN apk add --no-cache curl`
# ดูรายละเอียดเพิ่มใน Docker บท 01 Part 3.12
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:3000/health || exit 1"]
interval: 30s # ตรวจทุก 30 วินาที
timeout: 5s # รอผลไม่เกิน 5 วินาที
retries: 3 # fail 3 ครั้งติด = ป่วย
start_period: 10s # ช่วง warm-up ช่วงแรก ไม่นับ fail
# --- resource limits ---
# ✅ ตั้งแต่ Docker Compose v2.17+: `deploy.resources.limits` ทำงานได้กับ
# `docker compose up` ปกติโดยไม่ต้อง flag พิเศษ — memory และ cpus limits
# ถูกบังคับใช้จริง (ตรวจเวอร์ชันด้วย `docker compose version`)
# 📝 Docker Swarm (เครื่องมือ orchestration เก่าของ Docker สำหรับรัน container
# ข้ามหลายเครื่อง — ปัจจุบัน maintenance mode แล้ว) ใช้ `deploy:` block เช่นกัน
# แต่มี field เพิ่มเติมที่ Compose ปกติไม่รองรับ เช่น replicas, placement
deploy:
resources:
limits:
cpus: "1" # ใช้ CPU ไม่เกิน 1 core
memory: 512M # ใช้ RAM ไม่เกิน 512 MB
# --- user ---
user: "1000:1000"
# --- command override (override CMD ใน Dockerfile) ---
command: ["node", "server.js", "--verbose"]ไม่ต้องใส่ทุก option — ใส่เท่าที่ต้องการ ส่วนใหญ่มี default ที่ดีอยู่แล้ว
Part 6: depends_on + healthcheck — เริ่มให้ถูกลำดับ
6.1 ปัญหา
api ต้องการ db พร้อมก่อนถึงจะทำงานได้ — ถ้า api เริ่มก่อน db พร้อม → api เชื่อม database ไม่ได้ → crash
6.2 depends_on แบบธรรมดา — ยังไม่พอ
yaml
api:
depends_on:
- dbอันนี้บอกแค่ "เริ่ม db ก่อน api" — แต่ปัญหาคือ "เริ่ม container db" ≠ "database พร้อมรับ connection"
PostgreSQL ใช้เวลา 2-5 วินาทีหลัง container เริ่ม กว่าจะพร้อมรับ connection จริง — ระหว่างนั้น api อาจเริ่มไปแล้วและ crash
6.3 depends_on + healthcheck — วิธีที่ถูก
yaml
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
api:
image: myapi:v1
depends_on:
db:
condition: service_healthy # ← รอจน db "healthy" จริง ๆอธิบาย:
dbมีhealthcheck— ใช้คำสั่งpg_isready(อ่านว่า "พีจี-อิส-เรดดี้" มาจาก "Postgres is ready?")pg_isreadyเป็นเครื่องมือของ PostgreSQL ที่เช็คว่า database พร้อมรับ connection หรือยังapiมีdepends_onแบบระบุcondition: service_healthy— แปลว่า "อย่าเริ่ม api จนกว่า db จะ healthcheck ผ่าน"
⚠️
condition: service_healthyต้องคู่กับ healthcheck เสมอ — ถ้า dependency ไม่มีhealthcheck:กำหนดไว้ Compose จะ error ว่า "service has no healthcheck"
condition มี 3 แบบ:
service_started— แค่ container เริ่ม (default)service_healthy— รอจน healthcheck ผ่าน ⭐ ใช้บ่อยสุดservice_completed_successfully— รอจน container ทำงานเสร็จและ exit 0 (สำหรับ container ที่ทำงานเป็น job)
Part 7: Volumes + Networks ใน Compose
7.1 Volumes
yaml
services:
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data # ใช้ named volume
volumes:
pgdata: # ประกาศ volume ตรงนี้
redis-data:
driver: local
external-vol:
external: true # ใช้ volume ที่สร้างไว้แล้วข้างนอกทุก named volume ที่ service ใช้ ต้องประกาศใน section volumes: ด้วย
7.2 Networks
ถ้าไม่กำหนดอะไร — Compose สร้าง network เดียวให้ทุก service (พอใช้สำหรับงานง่าย ๆ)
ถ้าต้องการ network isolation (จากบท 02):
yaml
services:
web:
networks: [frontend]
api:
networks: [frontend, backend]
db:
networks: [backend]
networks:
frontend:
backend:
internal: true # network นี้ออก internet ไม่ได้ผลคือ: web คุย db ตรง ๆ ไม่ได้ (ไม่มี network ร่วม) — ต้องผ่าน api
Part 8: Stack สมบูรณ์ — ตัวอย่าง production จริง
yaml
services:
# --- Reverse proxy ---
proxy:
image: nginx:1.27-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
# ⚠️ mount เป็น conf.d/default.conf ไม่ใช่ /etc/nginx/nginx.conf
# เพื่อไม่ทับ events{} / http{} block หลัก
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
# ⚠️ cert ต้องสร้างก่อนแยกต่างหาก — เช่น ด้วย certbot/acme.sh (Let's Encrypt)
# หรือ cert manager — compose ไม่ได้ออก cert ให้ ต้องจัดการ rotation เองด้วย
- ./certs:/etc/nginx/ssl:ro # path มาตรฐานของ nginx SSL
networks: [frontend]
depends_on:
- api
# --- Backend API ---
api:
build:
context: ./api
target: production
restart: unless-stopped
environment:
DATABASE_HOST: db
DATABASE_PASSWORD: ${DB_PASSWORD} # อ่านจากไฟล์ .env
REDIS_HOST: cache
networks: [frontend, backend]
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
healthcheck:
# ✅ ใช้ wget — รองรับ alpine image โดยไม่ต้องลงเพิ่ม
# ถ้า api image มี curl: ["CMD", "curl", "-f", "http://localhost:3000/health"]
test: ["CMD-SHELL", "wget -qO- http://localhost:3000/health || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
deploy:
resources:
limits:
cpus: "1"
memory: 512M
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# --- Database ---
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: appuser
POSTGRES_DB: myapp
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
networks: [backend]
healthcheck:
# -d myapp = ระบุ database ให้ตรง — ไม่งั้น pg_isready จะลอง connect db ชื่อ appuser
# (default) ซึ่งอาจไม่มี
test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"]
interval: 5s
timeout: 3s
retries: 5
# --- Cache ---
cache:
image: redis:7-alpine
restart: unless-stopped
# AOF = append-only file (persist ทุก write). Redis 7+ ใช้ AOF+RDB hybrid โดย default แล้ว
# ตัวอย่างนี้บังคับเปิด AOF ชัดเจน — ใช้ได้เมื่อ Redis ทำหน้าที่เป็น "ที่เก็บข้อมูลหลัก"
# ⚠️ ถ้าใช้ Redis เป็น cache อย่างเดียว (ข้อมูลหายได้) → ไม่ต้องใช้ --appendonly yes
# เพราะ AOF เพิ่ม write overhead โดยไม่มีประโยชน์สำหรับ cache
command: redis-server --appendonly yes
volumes:
- redis-data:/data
networks: [backend]
volumes:
pgdata:
redis-data:
networks:
frontend:
backend:
internal: trueอ่าน compose ไฟล์นี้แล้วจะเห็นภาพระบบทั้งหมด — ดีกว่าไล่อ่านคำสั่ง docker run หลายสิบบรรทัด
Part 9: Environment Variables + .env
อย่าฝังรหัสผ่านไว้ใน compose.yaml ตรง ๆ (เพราะไฟล์นี้มัก commit เข้า Git)
ใช้ไฟล์ .env (อยู่ข้าง ๆ compose.yaml):
.env:
DB_PASSWORD=super-secret-123
TAG=v1.2.3compose.yaml:
yaml
services:
db:
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} # ${} ดึงค่าจาก .env
api:
image: myapi:${TAG}แล้วใส่ .env ใน .gitignore — รหัสผ่านไม่หลุดเข้า Git
💡 Compose โหลด
.envอัตโนมัติ โดยไม่ต้องระบุenv_file:ทุกครั้งที่สั่งdocker compose upCompose จะอ่าน.envที่อยู่ข้าง ๆcompose.yamlเสมอ — ทำให้ environment บน CI ที่ไม่มีไฟล์.envอาจต่างจาก local ใช้docker compose configเพื่อตรวจว่า variable ถูก resolve ถูกต้อง
9.1 Compose Secrets — วิธีที่ปลอดภัยกว่า env var
.env ดีกว่าฝัง plaintext ใน YAML แต่ยังมีปัญหา: env var ของ container มองเห็นได้ ผ่าน docker inspect, ไฟล์ระบบที่แสดง environment ของ process (โปรแกรมใด ๆ บนเครื่องอาจอ่านได้), error message ที่ dump env, log
secrets: block ของ Compose แก้ปัญหานี้ — mount ความลับเป็น ไฟล์ ที่ /run/secrets/<name> แทน env var:
yaml
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: appuser
POSTGRES_DB: myapp
# postgres image รองรับ _FILE suffix — อ่านค่าจากไฟล์แทน env ตรง ๆ
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txt # ไฟล์ที่เก็บ password (chmod 600, .gitignore)ข้อดี:
docker inspectไม่เห็นค่า — เห็นแค่ว่ามี secret mount ไว้- ไฟล์อยู่ใน tmpfs (RAM) ของ container ไม่เขียนลง disk
- เปลี่ยน secret = แก้ไฟล์ → restart service เดียว ไม่ต้อง rebuild image
💡 production จริงจัง — เก็บ secret ใน vault (HashiCorp Vault, AWS Secrets Manager, Doppler) แล้ว inject เข้า container ตอน start
Part 10: คำสั่ง Compose ที่ใช้บ่อย
bash
# เริ่มทุก service (สร้าง + รัน) แบบเบื้องหลัง
docker compose up -d
# เริ่ม + build image ใหม่ก่อน
docker compose up -d --build
# ดู service ที่รันอยู่
docker compose ps
# ดู log
docker compose logs # ทั้งหมด
docker compose logs api # service เดียว
docker compose logs -f api # ตามแบบ real-time
# หยุดทุก service (แต่ยังเก็บ container ไว้)
docker compose stop
# เริ่มที่หยุดไว้
docker compose start
# restart
docker compose restart api
# หยุด + ลบ container + ลบ network
docker compose down
# down + ลบ volume ด้วย (⚠️ ข้อมูลหาย!)
docker compose down -v
# รันคำสั่งใน service ที่กำลังรัน
docker compose exec api sh
# รันคำสั่งใน container ใหม่ชั่วคราว
# 💡 ใส่ --rm เสมอ — ไม่งั้น Compose จะเก็บ container ที่ run ทิ้งไว้สะสมเรื่อย ๆ
docker compose run --rm api npm test
# ดู config ที่ resolved แล้ว (เช็คว่า .env ถูกแทนค่าถูกไหม)
docker compose config
# ดึง image ล่าสุด
docker compose pull
# build อย่างเดียว
docker compose build⚠️ v1 ตายแล้ว — ใช้
docker compose(มี space) เท่านั้น
docker compose(มี space) = v2 — เวอร์ชันใหม่ที่ติดมากับ Docker ใช้งานได้โดยตรง — ใช้ตัวนี้docker-compose(มีขีด) = v1 — เวอร์ชันเก่าที่ต้องลงแยก ปัจจุบัน EOL ตั้งแต่ 2023 ถูกถอดออกจาก Docker Desktop รุ่นใหม่แล้วตรวจรุ่นที่ติดตั้ง:
docker compose version— ขึ้นDocker Compose version v2.x.x= พร้อมใช้
Part 11: Profile — เปิด service บางตัวเฉพาะบางสถานการณ์
บางทีเราอยากมี service ที่เปิดเฉพาะตอน debug — ไม่อยากให้รันตอนปกติ
yaml
services:
api:
image: myapi
db:
image: postgres:16
# tool สำหรับดู database — เปิดเฉพาะตอนต้องการ
adminer:
image: adminer
ports: ["8081:8080"]
profiles: [debug] # ← อยู่ใน profile "debug"bash
docker compose up -d # รันแค่ api + db (adminer ไม่รัน)
docker compose --profile debug up -d # รัน api + db + adminerservice ที่มี profiles: จะไม่รัน เว้นแต่เราระบุ profile นั้น — ใช้แยก dev tool, test service ออกจาก service หลัก
Part 11.5: develop.watch — Dev Workflow แบบใหม่ (Compose v2.22+)
วิธีดั้งเดิม dev ด้วย Compose คือ bind mount source code เข้า container แล้วใช้ nodemon/air/ts-node-dev ดู file change — ใช้ได้ แต่:
- bind mount บน Mac/Windows ช้า (filesystem ข้าม VM)
- ต้องมี watcher อยู่ใน container — เพิ่ม dependency
- เปลี่ยน
package.jsonต้อง rebuild image เอง
develop.watch (Compose v2.22+, ปี 2026 = วิธีมาตรฐาน) ทำใน Compose โดยตรง:
yaml
services:
api:
build: ./api
develop:
watch:
- path: ./api/src # ดูโฟลเดอร์นี้
action: sync # เปลี่ยน → sync ไฟล์เข้า container ทันที
target: /app/src
- path: ./api/package.json # เปลี่ยน package.json
action: rebuild # → rebuild image ใหม่อัตโนมัติ
- path: ./api/static
action: sync+restart # → sync แล้ว restart container
target: /app/staticสั่งด้วย:
bash
docker compose watchข้อดี:
- ไม่ต้อง bind mount → เร็วบน Mac/Windows
- 3 action ครอบคลุมทุกกรณี:
sync(ไฟล์ source),rebuild(manifest เปลี่ยน),sync+restart(config ที่ต้อง reload) - ไม่ต้องมี watcher ใน container
💡 ตอนนี้แทบทุก dev workflow ที่เคยใช้
volumes: [.:/app]เริ่มย้ายมาใช้develop.watchแทน — production ยังคงใช้ image แบบ build เต็มเหมือนเดิม
Part 12: Override File — config ต่างกันตาม environment
🚀 โซนขั้นสูง — ข้ามได้ Part 12 (override file สำหรับ dev/prod) เป็นเทคนิคที่ใช้ตอนระบบเริ่มโตและต้องแยก config หลาย environment — มือใหม่ที่เพิ่งหัดเขียน compose ข้ามไปก่อนได้ แล้วค่อยกลับมาตอนต้องแยก dev/prod จริง
ปัญหา: dev กับ production ต้องการ config ต่างกัน (dev อยาก mount source code, production ไม่อยาก)
Compose แก้ด้วยการ merge หลายไฟล์
compose.yaml — config พื้นฐานร่วมกัน:
yaml
services:
api:
image: myapi
environment:
LOG_LEVEL: infocompose.override.yaml — config สำหรับ dev:
💡 โหลดอัตโนมัติเสมอ ไม่ต้องระบุ flag — ทุกครั้งที่สั่ง
docker compose up(ไม่มี-f) Compose จะอ่านcompose.yaml+compose.override.yamlแล้ว merge ให้เอง ถ้าอยากใช้ไฟล์อื่นแทน override default ต้องระบุด้วย-fทั้งสองไฟล์
yaml
services:
api:
build: . # dev: build จาก source
volumes:
- .:/app # dev: mount source code
environment:
LOG_LEVEL: debug # dev: log ละเอียดcompose.prod.yaml — config สำหรับ production:
yaml
services:
api:
image: ghcr.io/acme/myapi:v1.0.0
environment:
LOG_LEVEL: warnใช้งาน:
bash
# dev — Compose โหลด compose.yaml + compose.override.yaml อัตโนมัติ
docker compose up -d
# production — ระบุไฟล์เอง
docker compose -f compose.yaml -f compose.prod.yaml up -dเวลา merge — ไฟล์ที่ระบุทีหลังจะ "ทับ" ไฟล์ก่อนหน้า
Part 13: Scaling
bash
docker compose up -d --scale api=3รัน api 3 ตัวพร้อมกัน
⚠️ ข้อจำกัด: Compose ทำ scaling ได้พื้นฐานเท่านั้น — ไม่มี load balancer ในตัว, ไม่มี auto-restart ข้ามเครื่อง, ไม่มี rolling update
→ ถ้าต้องการ scaling/orchestration จริงจัง → ใช้ Kubernetes (หมวดถัดไป!) — Compose เหมาะกับ dev + production เล็ก ๆ
Part 14: Logging Config
yaml
services:
api:
logging:
driver: json-file
options:
max-size: "10m" # log แต่ละไฟล์ไม่เกิน 10MB
max-file: "3" # เก็บไม่เกิน 3 ไฟล์ทำไมสำคัญ: ถ้าไม่จำกัด — log ไฟล์โตไม่หยุดจนเต็ม disk → server ล่ม. ตั้ง max-size + max-file เสมอ
Part 15: Compose vs Kubernetes — เลือกตอนไหน
| Docker Compose | Kubernetes | |
|---|---|---|
| ความซับซ้อน | ง่าย | ยาก (เรียนนาน) |
| เครื่อง | 1 เครื่อง | หลายเครื่อง (cluster) |
| Auto-restart | ระดับ container | ระดับ cluster |
| Auto-scaling | ❌ | ✅ |
| Rolling update | ❌ (ทำเองได้บ้าง) | ✅ |
| Self-healing | จำกัด | ✅ |
| เหมาะกับ | dev, prototype, prod เล็ก | production จริงจัง, scale |
→ Compose = เริ่มต้นที่นี่ ใช้ dev เสมอ, prod เล็ก ๆ ก็ได้ → Kubernetes = เมื่อระบบโต ต้อง scale, ต้อง HA — เรียนหมวด kubernetes/ ต่อ
📝 HA (High Availability — "ความพร้อมใช้งานสูง") = ระบบที่ออกแบบให้ "แทบไม่ล่ม" ถ้าเครื่องหนึ่งพังก็ยังมีตัวสำรองทำงานต่อทันที ผู้ใช้ไม่รู้สึกว่ามีอะไรเกิดขึ้น
ข่าวดี: ความรู้ Compose ปูพื้นไป K8s ได้ดีมาก — concept คล้ายกัน (service, network, volume, healthcheck)
Part 16: Lab
Lab 1: Stack แรก
แล็บแรกพาเขียน compose.yaml หลายบริการในไฟล์เดียว — web (nginx) + api ที่ผูกกันด้วย depends_on แล้วสั่งทั้ง stack ขึ้น/ลงด้วย docker compose up/down คำสั่งเดียว แทนการ docker run ทีละตัว:
compose.yaml:
yaml
services:
web:
image: nginx:1.25-alpine
ports: ["8080:80"]
depends_on: [api]
api:
image: hashicorp/http-echo
command: ["-text=Hello from API"]bash
docker compose up -d
docker compose ps
docker compose logs⚠️ ก่อน curl: Lab นี้ สาธิตแค่
depends_on(ลำดับการเริ่ม service) เท่านั้น —web(nginx) ยังไม่ได้ตั้งค่าให้ proxy ต่อไปยังapiจริง ๆ การรันcurl http://localhost:8080จะได้ หน้า default ของ nginx ไม่ใช่ข้อความจาก api — นั่นคือผลที่ถูกต้องของ Lab นี้✅ วิธีตรวจว่า Lab สำเร็จ: รัน
docker compose ps— ถ้าเห็นทั้งwebและapiอยู่ใน staterunning= สำเร็จ
bash
# ทดสอบ api โดยตรง (ไม่ผ่าน nginx):
docker compose exec api /bin/echo "api is running"
# หรือดู log api:
docker compose logs api
# ทดสอบ nginx (จะได้หน้า default — ถูกต้องตามที่ควรเป็น):
curl http://localhost:8080
docker compose down📝 การต่อ nginx ให้ proxy ไป backend ทำตาม "Reverse Proxy Pattern" ในบท 02 Part 10 (ต้อง mount
nginx.confที่ตั้งproxy_pass)
Lab 2: App + DB + healthcheck
แล็บนี้ยกระดับด้วยการใช้ healthcheck คุม dependency จริง — ตั้ง depends_on ให้ adminer รอจน db ผ่าน healthcheck (พร้อมรับ connection) ก่อนค่อยเริ่ม แก้ปัญหาคลาสสิกที่แอปพังเพราะสตาร์ตเร็วกว่า database:
yaml
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: secret
volumes:
- data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 5
adminer:
image: adminer
ports: ["8081:8080"]
depends_on:
db:
condition: service_healthy
volumes:
data:bash
docker compose up -d
# เปิด http://localhost:8081 → login server=db user=postgres password=secretLab 3: dev/prod override
ทำตาม Part 12 — สร้าง 3 ไฟล์ — ลองรันทั้ง 2 mode เทียบกัน
Part 17: Checkpoint
- ปัญหาของ
docker runทีละตัว มีอะไรบ้าง? - compose.yaml มี 3 section หลักอะไร?
- ทำไม service ใน compose เรียกชื่อกันได้โดยไม่ต้องสร้าง network เอง?
- กฎ YAML ที่ห้ามผิดคืออะไร?
depends_onธรรมดา ทำไมยังไม่พอ?condition: service_healthyทำอะไร?.envใช้ทำอะไร? ทำไมไม่ฝัง password ใน compose.yaml?docker compose downกับdown -vต่างกันยังไง?- Profile ใช้ทำอะไร?
- Compose vs Kubernetes — เลือกตอนไหน?
Part 18: สรุปบทนี้
- Docker Compose = นิยามหลาย container ในไฟล์
compose.yamlไฟล์เดียว →docker compose upครั้งเดียวจบ - Compose สร้าง network ให้อัตโนมัติ → service เรียกชื่อกันได้
- เขียนด้วย YAML — เยื้องด้วย space ห้าม Tab
depends_on+healthcheck+condition: service_healthy= เริ่มให้ถูกลำดับ.env= เก็บความลับแยกจาก compose.yaml- Profile = service ที่เปิดเฉพาะบางสถานการณ์
- Override file = config ต่างกันตาม dev/prod
- Compose เหมาะกับ dev + prod เล็ก — prod ใหญ่จริงจังใช้ Kubernetes
บทต่อไป (สุดท้ายของหมวด Docker) — Security + Optimization