/` | `/sys/fs/cgroup/` |
| Rootless support | จำกัด | รองรับเต็ม |
| PSI | ไม่มี | มี |
| Docker | รองรับ (legacy) | default ใน Docker 20.10+ |
---
## ตัวอย่างการจำกัด memory ด้วย cgroups v2
```bash
# สร้าง cgroup ใหม่ในชื่อ "demo"
sudo mkdir /sys/fs/cgroup/demo
# จำกัด memory ที่ 100 MB
echo "100M" | sudo tee /sys/fs/cgroup/demo/memory.max
# ย้าย process ปัจจุบันเข้า cgroup
echo $$ | sudo tee /sys/fs/cgroup/demo/cgroup.procs
# ทดสอบ – พยายามใช้ memory เกินจะถูก OOM kill
stress --vm 1 --vm-bytes 200M --timeout 10
```
---
## 11.1.5 Union File System
**UnionFS** เป็นเทคนิคที่รวมหลาย directory (layers) เข้าเป็น filesystem เดียวแบบ logically merged
- layer ด้านบนซ้อนทับ layer ด้านล่าง
- การเปลี่ยนแปลงไฟล์จะถูกเขียนลง layer บนสุด
- ใช้กลไก **Copy-on-Write (CoW)**
ระบบที่รองรับ: OverlayFS, AUFS, Btrfs
---
## โครงสร้าง Layers
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836','primaryTextColor':'#ebdbb2','primaryBorderColor':'#fabd2f',
'lineColor':'#fe8019','secondaryColor':'#504945','background':'#282828','mainBkg':'#3c3836'
}}}%%
flowchart TB
Top["Container Layer
(RW)
เก็บการเปลี่ยนแปลง"]
L3["Image Layer 3
(read-only)
ไฟล์ของแอป"]
L2["Image Layer 2
(read-only)
pip install"]
L1["Image Layer 1
(read-only)
apt install python"]
Base["Base Layer
(read-only)
ubuntu:22.04"]
Top --> L3 --> L2 --> L1 --> Base
---
## OverlayFS
OverlayFS เป็นมาตรฐานปัจจุบันบน Linux modern (kernel 4.0+) มีโครงสร้างหลัก 4 directory:
- **lowerdir** — layer ด้านล่าง (read-only, อาจมีหลายชั้น)
- **upperdir** — layer ด้านบน (read-write)
- **workdir** — directory ทำงานภายใน (atomic operation)
- **merged** — จุดรวมที่ user มองเห็น
---
## ตัวอย่างการสร้าง OverlayFS ด้วยมือ
```bash
# สร้าง directory ทดลอง
mkdir -p /tmp/overlay/{lower,upper,work,merged}
echo "ข้อมูลจาก lower" > /tmp/overlay/lower/file.txt
# mount แบบ overlay
sudo mount -t overlay overlay \
-o lowerdir=/tmp/overlay/lower,\
upperdir=/tmp/overlay/upper,\
workdir=/tmp/overlay/work \
/tmp/overlay/merged
```
---
## ทดสอบ Copy-on-Write
```bash
# เขียนทับไฟล์
echo "ข้อมูลใหม่" > /tmp/overlay/merged/file.txt
# ตรวจสอบ
cat /tmp/overlay/lower/file.txt # ไม่เปลี่ยน
cat /tmp/overlay/upper/file.txt # มีไฟล์ใหม่ (CoW)
cat /tmp/overlay/merged/file.txt # เห็นค่าใหม่
```
---
## 11.1.6 กลไกความปลอดภัยเพิ่มเติม
นอกจาก namespace และ cgroups Linux ยังมีกลไกความปลอดภัยเพิ่ม:
- **Linux Capabilities** — แบ่งสิทธิ์ root เป็นหน่วยย่อย เช่น CAP_NET_ADMIN
- **Seccomp** — ตัวกรอง system call ระดับ kernel ผ่าน BPF
- **AppArmor** — Mandatory Access Control แบบ path-based (Ubuntu/Debian)
- **SELinux** — MAC แบบ label-based (RHEL/Fedora) ละเอียดและซับซ้อนกว่า
---
## ตัวอย่างจำกัด Capability
```bash
# Drop ทุก capability แล้วเพิ่มเฉพาะที่จำเป็น
docker run --rm \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
--security-opt=no-new-privileges \
nginx:alpine
```
Docker จะ drop capabilities ส่วนใหญ่ทิ้งโดย default
---
## 11.1.7 OCI Specification
**Open Container Initiative (OCI)** เป็นองค์กรกลางกำหนดมาตรฐาน 3 spec หลัก:
1. **Runtime Specification** — bundle (rootfs + config.json) ถูก execute อย่างไร
- เครื่องมือ: runc, crun, youki
2. **Image Specification** — โครงสร้าง image (manifest, layer, config)
3. **Distribution Specification** — HTTP API ของ registry
ทำให้ทุกวันนี้สร้าง image ด้วย buildah แล้วรันด้วย podman หรือ push ไป Harbor และ pull ด้วย Docker ได้
---
# 11.2 สถาปัตยกรรม Docker
---
## 11.2 สถาปัตยกรรม Docker
Docker ไม่ใช่โปรแกรมเดียว แต่เป็นระบบของหลาย component ที่ทำงานร่วมกัน
ประกอบด้วย:
- Docker Engine (dockerd)
- Docker Client (docker CLI)
- containerd และ runc
- Docker Registry
- Storage Driver และ Network Driver
---
## โครงสร้างของ Docker
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836','primaryTextColor':'#ebdbb2','primaryBorderColor':'#fabd2f',
'lineColor':'#fe8019','secondaryColor':'#504945','background':'#282828','mainBkg':'#3c3836'
}}}%%
flowchart TB
User["ผู้ใช้"] --> CLI["docker CLI"]
CLI -->|REST API| Daemon["dockerd"]
Daemon --> Containerd["containerd"]
Containerd --> Shim["containerd-shim"]
Shim --> Runc["runc"]
Runc --> Kernel["Linux Kernel
(namespace + cgroups)"]
Daemon -.->|pull/push| Registry["Docker Registry"]
Daemon -.-> Storage["Storage Driver"]
Daemon -.-> Network["Network Driver"]
---
## 11.2.1 Docker Engine (dockerd)
**dockerd** เป็น long-running daemon ที่รับคำสั่งจาก client ผ่าน REST API
โดย default คือ UNIX socket `/var/run/docker.sock`
หน้าที่หลัก:
- จัดการ image (pull, build, push)
- บริหาร lifecycle ของ container
- จัดการ network และ volume
- คุยกับ containerd เพื่อ execute container
---
## 11.2.2 Docker Client (docker CLI)
**docker CLI** คือคำสั่งที่ผู้ใช้พิมพ์ใน terminal
- เป็น wrapper แปลงคำสั่งเป็น HTTP request ส่งไป dockerd
- Client และ daemon **ไม่จำเป็น**ต้องอยู่เครื่องเดียวกัน
```bash
# คุย Docker ที่เครื่อง remote ผ่าน SSH
export DOCKER_HOST="ssh://user@remote-server"
docker ps
# คุยผ่าน TCP (ต้องเปิด tls)
export DOCKER_HOST="tcp://192.168.1.100:2376"
```
---
## 11.2.3 containerd และ runc
ใต้ Docker Engine ยังมีอีกหลายชั้น:
- **containerd** — high-level container runtime จัดการ image, snapshot, network, lifecycle (CNCF project)
- **containerd-shim** — process กลางที่ทำให้ restart containerd ได้โดยไม่ kill container
- **runc** — low-level runtime ตามมาตรฐาน OCI
- สร้าง namespace, ตั้ง cgroups, exec process จริง
---
## 11.2.4 Docker Registry
**Registry** คือบริการเก็บ image แบบ HTTP (ตามมาตรฐาน OCI Distribution)
- เมื่อรัน `docker pull nginx` Docker จะติดต่อ registry (default: Docker Hub)
- ดาวน์โหลด manifest และ layer ทั้งหมด
- แต่ละ layer สามารถถูก reuse โดย image อื่น เพื่อประหยัดพื้นที่
---
## 11.2.5 Docker Desktop Architecture
เนื่องจาก container ต้องการ Linux kernel ดังนั้นบน macOS และ Windows Docker Desktop จะ:
1. รัน Linux VM ขนาดเล็ก
- macOS: HyperKit / Virtualization.framework
- Windows: WSL2 / Hyper-V
2. รัน dockerd ภายใน VM
3. Docker CLI บนเครื่อง host ติดต่อ dockerd ผ่าน proxy
ผล: bind mount จาก host **ช้ากว่า** Linux native
---
# 11.3 การติดตั้งและตั้งค่า Docker
---
## 11.3.1 ติดตั้งบน Ubuntu/Debian
```bash
# 1. ติดตั้ง dependency
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release
# 2. เพิ่ม Docker GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
```
---
## ติดตั้ง Docker บน Ubuntu (ต่อ)
```bash
# 3. เพิ่ม repository
echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list
# 4. ติดตั้ง Docker Engine + Compose plugin
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# 5. ตรวจสอบ
sudo docker run hello-world
```
---
## ติดตั้งบน Fedora/RHEL และ Arch
**Fedora/RHEL:**
```bash
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo \
https://download.docker.com/linux/fedora/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io \
docker-compose-plugin
sudo systemctl enable --now docker
```
**Arch Linux:**
```bash
sudo pacman -S docker docker-compose docker-buildx
sudo systemctl enable --now docker.service
```
---
## 11.3.2 Docker Desktop
**Docker Desktop** เป็นแอปพลิเคชันแบบ GUI ที่รวม:
- Docker Engine
- Compose
- Kubernetes
- Dashboard
แพลตฟอร์มที่รองรับ:
- **Windows** — ต้องเปิดใช้ WSL2 ก่อน
- **macOS** — รองรับทั้ง Intel และ Apple Silicon
- **Linux** — เป็นทางเลือกใหม่ (ไม่จำเป็นต้องใช้)
---
## 11.3.3 Rootless Docker
**Rootless Docker** ให้ user ทั่วไป (ไม่ต้องเป็น root) สามารถรัน Docker daemon ในชื่อของตัวเองได้
เพิ่มความปลอดภัย: หากมี exploit ใน container ผู้โจมตีจะได้แค่สิทธิ์ของ user คนนั้น
```bash
sudo apt install -y uidmap dbus-user-session
curl -fsSL https://get.docker.com/rootless | sh
echo 'export PATH=$HOME/bin:$PATH' >> ~/.bashrc
echo 'export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock' >> ~/.bashrc
systemctl --user enable --now docker
```
---
## 11.3.4 การเพิ่ม User เข้า docker group
วิธีให้ user ทั่วไปรัน docker ได้โดยไม่ใช้ sudo ทุกครั้ง:
```bash
sudo usermod -aG docker $USER
# logout และ login ใหม่ หรือใช้
newgrp docker
```
**⚠️ คำเตือน:** การอยู่ใน group `docker` มีค่าเทียบเท่า root เพราะสามารถ mount `/` ของ host เข้า container ได้
ในระบบ production แนะนำให้ใช้ **Rootless Docker** หรือ **Podman** แทน
---
## 11.3.5 Configuration daemon.json
ไฟล์ตั้งค่าหลักของ daemon: `/etc/docker/daemon.json`
```json
{
"data-root": "/var/lib/docker",
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"registry-mirrors": ["https://mirror.gcr.io"],
"live-restore": true
}
```
หลังแก้ไขต้องรัน `sudo systemctl restart docker`
---
# 11.4 Image และ Container
---
## 11.4.1 คำสั่งพื้นฐาน
```bash
# ดึง image จาก registry
docker pull nginx:alpine
# สร้างและรัน container ใหม่
docker run -d --name web -p 8080:80 nginx:alpine
# -d = detached (background)
# --name = ตั้งชื่อ container
# -p host:container = port mapping
# หยุด container
docker stop web
# เริ่มใหม่
docker start web
```
---
## คำสั่งจัดการ Container (ต่อ)
```bash
# restart = stop + start
docker restart web
# ลบ container (ต้อง stop ก่อน หรือใช้ -f)
docker rm -f web
# ลบ image
docker rmi nginx:alpine
```
---
## 11.4.2 คำสั่งดูข้อมูล
```bash
# ดู container ที่กำลังรัน
docker ps
docker ps -a # รวมที่หยุดแล้ว
docker ps --filter "status=exited"
# ดู image ทั้งหมด
docker images
# ดู log
docker logs web
docker logs -f --tail 100 web # follow + 100 บรรทัดล่าสุด
```
---
## คำสั่งดูข้อมูล (ต่อ)
```bash
# เข้าไปใน container เพื่อ debug
docker exec -it web /bin/sh
# -i = interactive, -t = TTY
# ดูข้อมูลทั้งหมดของ container/image (JSON)
docker inspect web
# ดูการใช้ทรัพยากรแบบ real-time
docker stats
# ดู process ภายใน container
docker top web
```
---
## 11.4.3 Container Lifecycle
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836','primaryTextColor':'#ebdbb2','primaryBorderColor':'#fabd2f',
'lineColor':'#fe8019','secondaryColor':'#504945','background':'#282828','mainBkg':'#3c3836'
}}}%%
stateDiagram-v2
[*] --> Created: docker create
Created --> Running: docker start
Running --> Paused: docker pause
Paused --> Running: docker unpause
Running --> Stopped: docker stop
Stopped --> Running: docker start
Running --> Exited: process exits
Exited --> Running: docker start
Stopped --> [*]: docker rm
Exited --> [*]: docker rm
---
## 11.4.4 Detached vs Interactive Mode
- **`-d` (detached)** — รันใน background ทันที เหมาะกับ service เช่น web server
- **`-it` (interactive + tty)** — รันแบบมี shell โต้ตอบ เหมาะกับ debug หรือ sandbox
```bash
# Service mode
docker run -d --name api -p 3000:3000 myapp:latest
# Sandbox mode
docker run -it --rm ubuntu:22.04 /bin/bash
# --rm = ลบ container อัตโนมัติเมื่อออก
```
---
## 11.4.5 Port Mapping
`-p` แมป port ของ host ไปยัง port ใน container:
```bash
# host 8080 → container 80
docker run -d -p 8080:80 nginx
# bind เฉพาะ localhost
docker run -d -p 127.0.0.1:8080:80 nginx
# TCP + UDP
docker run -d -p 8080:80/tcp -p 8080:80/udp myapp
# auto map ทุก EXPOSE port
docker run -d -P nginx
```
---
## 11.4.6 Environment Variable
ส่ง environment variable เข้า container:
```bash
# ทีละตัว
docker run -d \
-e DB_HOST=db.example.com \
-e DB_USER=admin \
-e DB_PASS=secret123 \
myapp
# จากไฟล์
cat > .env <
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836','primaryTextColor':'#ebdbb2','primaryBorderColor':'#fabd2f',
'lineColor':'#fe8019','secondaryColor':'#504945','background':'#282828','mainBkg':'#3c3836'
}}}%%
flowchart LR
subgraph Host["Host Filesystem"]
A["/var/lib/docker/
volumes/myvol"]
B["/home/moo/project
(user path)"]
C["RAM
(ไม่อยู่ใน disk)"]
end
subgraph Container["Container"]
V["Volume Mount
/data"]
BM["Bind Mount
/app"]
T["tmpfs Mount
/tmp"]
end
A === V
B === BM
C === T
---
## เปรียบเทียบ Storage ทั้ง 3 แบบ
| ประเภท | จัดการโดย | ใช้เมื่อ |
| --- | --- | --- |
| **Volume** | Docker | ข้อมูล persistent ของ DB, cache (production) |
| **Bind Mount** | User | development (mount source code), config |
| **tmpfs** | Kernel (RAM) | secret ชั่วคราว, ไฟล์ที่ไม่ต้องการคงอยู่ |
---
## 11.7.2 Volume Management
```bash
# สร้าง volume แบบมีชื่อ
docker volume create pgdata
# ดูทั้งหมด
docker volume ls
# ดูรายละเอียด
docker volume inspect pgdata
# mount เข้า container
docker run -d --name db \
-v pgdata:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:16
```
---
## Volume Management (ต่อ)
```bash
# mount แบบ bind (syntax ใหม่ --mount)
docker run -d --name app \
--mount type=bind,source="$(pwd)",target=/app \
--mount type=tmpfs,target=/tmp,tmpfs-size=64m \
myapp
# ลบ volume (ต้องไม่มี container ใช้อยู่)
docker volume rm pgdata
# ลบ volume ที่ไม่ถูกใช้แล้วทั้งหมด
docker volume prune
```
---
## 11.7.3 Volume Driver
Docker รองรับ volume driver หลายแบบ — local เป็น default แต่สามารถใช้ NFS, cloud ได้
```bash
# Volume แบบ NFS
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=192.168.1.100,rw \
--opt device=:/exports/data \
nfs-data
```
---
## 11.7.4 Backup และ Restore Volume
Docker ไม่มีคำสั่ง `volume backup` ในตัว แต่ใช้ container ชั่วคราวสำรองได้:
```bash
# Backup volume "pgdata" เป็น tar
docker run --rm \
-v pgdata:/source:ro \
-v "$(pwd)":/backup \
alpine \
tar czf /backup/pgdata-$(date +%Y%m%d).tar.gz -C /source .
```
---
## Restore Volume
```bash
# Restore กลับเข้า volume
docker volume create pgdata-restored
docker run --rm \
-v pgdata-restored:/target \
-v "$(pwd)":/backup \
alpine \
tar xzf /backup/pgdata-20260101.tar.gz -C /target
```
---
## 11.7.5 Storage Driver
Storage driver คือกลไกที่ Docker ใช้รวม layer ของ image
- **overlay2** — default และเร็วที่สุดบน Linux modern
- **btrfs** — สำหรับ Btrfs filesystem
- **zfs** — สำหรับ ZFS
```bash
# ดู storage driver ปัจจุบัน
docker info | grep "Storage Driver"
# เปลี่ยนใน /etc/docker/daemon.json
{ "storage-driver": "overlay2" }
```
---
# 11.8 Docker Networking
---
## 11.8.1 Network Driver
| Driver | Use Case | คุณสมบัติ |
| --- | --- | --- |
| **bridge** | default, single host | NAT, แยก network |
| **host** | ไม่มี isolation | ใช้ network ของ host ตรง |
| **none** | ไม่ต้องการ network | ไม่มี interface |
| **overlay** | multi-host (Swarm/K8s) | VXLAN |
| **macvlan** | container ดูเหมือน physical | ได้ MAC ตัวเอง |
| **ipvlan** | คล้าย macvlan ใช้ MAC ร่วม | เร็ว, L2/L3 |
---
## 11.8.2 การสร้าง Custom Network
```bash
# สร้าง bridge network ของตัวเอง
docker network create \
--driver bridge \
--subnet 172.30.0.0/16 \
--gateway 172.30.0.1 \
mynet
# ดูรายการ network
docker network ls
# ดูรายละเอียด
docker network inspect mynet
# รัน container ใน network ของเรา
docker run -d --name web --network mynet nginx
docker run -d --name db --network mynet postgres
```
---
## 11.8.3 Container-to-Container Communication
ใน custom bridge network Docker มี **embedded DNS**
ทำให้ container เรียกหากันด้วยชื่อได้:
```bash
# จาก container "web" ติดต่อ "db" ด้วยชื่อ
docker exec web ping -c 2 db
# PING db (172.30.0.3): 56 data bytes
# 64 bytes from 172.30.0.3: ...
```
**ข้อสำคัญ:** default bridge ไม่มี DNS ระหว่าง container — ต้องสร้าง custom network เอง
---
## 11.8.4 Port Publishing vs Exposing
- **EXPOSE** ใน Dockerfile หรือ `--expose`
- เป็นแค่ **metadata** บอกว่า image ใช้ port อะไร
- ไม่เปิด port จริง
- **`-p` / `--publish`**
- เปิด port จริงให้ host เข้าถึงได้
- มี NAT mapping จริง
---
## 11.8.5 Network Inspection
```bash
# ดูการเชื่อมต่อของ container
docker inspect -f \
'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web
# ดู port mapping
docker port web
# ทดสอบ DNS จากใน container
docker exec web nslookup db
# ตรวจ firewall/iptables ของ host (Linux)
sudo iptables -t nat -L DOCKER -n
```
---
# 11.9 Docker Compose
---
## 11.9 Docker Compose
**Docker Compose** ใช้สำหรับนิยามและรัน multi-container application ผ่านไฟล์ YAML
ข้อดี:
- แทนที่การพิมพ์คำสั่ง `docker run` หลายบรรทัด
- จัดการ environment, network, volume ในที่เดียว
- สามารถ version control ได้ง่าย
- รองรับ profile, override, secret
---
## 11.9.1 ไฟล์ compose.yaml (1)
```yaml
name: myapp
services:
web:
build:
context: .
dockerfile: Dockerfile
image: myapp:1.0
container_name: myapp-web
restart: unless-stopped
ports:
- "8080:8000"
environment:
DATABASE_URL: postgresql://app:secret@db:5432/myapp
REDIS_URL: redis://cache:6379/0
```
---
## ไฟล์ compose.yaml (2)
```yaml
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
networks:
- frontend
- backend
volumes:
- ./logs:/app/logs
```
---
## ไฟล์ compose.yaml (3)
```yaml
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
POSTGRES_DB: myapp
secrets:
- db_password
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d myapp"]
interval: 10s
retries: 5
```
---
## ไฟล์ compose.yaml (4)
```yaml
cache:
image: redis:7-alpine
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redisdata:/data
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
volumes:
pgdata:
redisdata:
```
---
## 11.9.2 คำสั่ง Compose
```bash
# เริ่มทุก service
docker compose up -d
# build ใหม่ก่อนเริ่ม
docker compose up -d --build
# ดูสถานะ
docker compose ps
# ดู log
docker compose logs -f
docker compose logs -f web
# exec เข้า service
docker compose exec web bash
docker compose exec db psql -U app -d myapp
```
---
## คำสั่ง Compose (ต่อ)
```bash
# rebuild service เดียว
docker compose build web
# stop ทุก service (container ยังอยู่)
docker compose stop
# down = stop + rm container + rm network
docker compose down
# down พร้อมลบ volume ด้วย (ระวัง!)
docker compose down -v
```
---
## 11.9.3 depends_on
ระบุลำดับการ start แต่ default แค่รอให้ container "เริ่มต้น" ไม่ใช่ "พร้อม"
ใช้ `condition` คู่กับ `healthcheck` เพื่อให้รอจริง:
```yaml
depends_on:
db:
condition: service_healthy # รอ healthcheck pass
migrate:
condition: service_completed_successfully # รอ exit 0
```
---
## 11.9.4 healthcheck
```yaml
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s # ตรวจทุก 30s
timeout: 5s # timeout ของแต่ละครั้ง
retries: 3 # fail กี่ครั้งจึงเป็น unhealthy
start_period: 30s # ระยะเริ่มต้น (fail ไม่นับ)
```
---
## 11.9.5 Restart Policy ใน Compose
```yaml
services:
web:
image: myapp
restart: unless-stopped
```
ค่าที่รองรับ:
- `no`
- `always`
- `on-failure[:max]`
- `unless-stopped`
---
## 11.9.6 Environment File (.env)
ไฟล์ `.env` ข้าง compose.yaml จะถูกอ่านเป็น default:
```bash
# .env
APP_VERSION=1.2.3
DB_PASSWORD=secret123
EXTERNAL_PORT=8080
```
```yaml
# compose.yaml
services:
web:
image: myapp:${APP_VERSION}
ports:
- "${EXTERNAL_PORT}:8000"
environment:
DB_PASSWORD: ${DB_PASSWORD}
```
---
## 11.9.7 Profile
ใช้ `profiles` ให้บาง service รันเฉพาะตอนต้องการ:
```yaml
services:
web:
image: myapp
# ไม่มี profile = รันเสมอ
debug-tools:
image: nicolaka/netshoot
profiles: ["debug"]
pgadmin:
image: dpage/pgadmin4
profiles: ["dev"]
ports: ["5050:80"]
```
---
## การใช้ Profile
```bash
# รันแค่ web
docker compose up -d
# web + pgadmin
docker compose --profile dev up -d
# ครบทั้งหมด
docker compose --profile dev --profile debug up
```
---
## 11.9.8 extends, include, override
Compose โหลด `compose.yaml` + `compose.override.yaml` อัตโนมัติ:
```yaml
# compose.yaml (base – production-like)
services:
web:
image: myapp:${TAG:-latest}
restart: unless-stopped
```
```yaml
# compose.override.yaml (dev – auto)
services:
web:
build: .
volumes:
- .:/app
environment:
DEBUG: "true"
```
---
## Override สำหรับ Production
```yaml
# compose.prod.yaml (เรียกใช้แบบเลือก)
services:
web:
deploy:
replicas: 3
logging:
driver: json-file
options:
max-size: "10m"
```
```bash
# Dev (auto-merge)
docker compose up -d
# Production
docker compose -f compose.yaml -f compose.prod.yaml up -d
```
---
## 11.9.9 Compose v1 vs v2
- **Compose v1** — `docker-compose` (มี dash)
- เขียนด้วย Python
- หยุดพัฒนาแล้ว
- **Compose v2** — `docker compose` (เว้นวรรค)
- เขียนด้วย Go เป็น plugin ของ Docker CLI
- เร็วกว่า
- รองรับ profile, include, secret ดีกว่า
- มาตรฐานปัจจุบัน
---
# 11.10 ตัวอย่างการประยุกต์ใช้งาน
---
## 11.10.1 LEMP Stack
```yaml
# compose.yaml – LEMP Stack
name: lemp-demo
services:
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
volumes:
- ./public:/var/www/html:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- php
```
---
## LEMP Stack (ต่อ)
```yaml
php:
image: php:8.3-fpm-alpine
volumes:
- ./public:/var/www/html
environment:
DB_HOST: db
DB_USER: app
DB_PASS: secret
db:
image: mariadb:11
environment:
MARIADB_ROOT_PASSWORD: rootsecret
MARIADB_DATABASE: app
MARIADB_USER: app
MARIADB_PASSWORD: secret
volumes:
- mariadb_data:/var/lib/mysql
volumes:
mariadb_data:
```
---
## ไฟล์ nginx.conf สำหรับ LEMP
```nginx
server {
listen 80;
server_name _;
root /var/www/html;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
include fastcgi_params;
}
}
```
---
## 11.10.2 MERN Stack
```yaml
# compose.yaml – MERN Stack
name: mern-demo
services:
mongo:
image: mongo:7
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: rootsecret
volumes:
- mongodata:/data/db
```
---
## MERN Stack (ต่อ)
```yaml
api:
build: ./backend
environment:
MONGO_URI: mongodb://root:rootsecret@mongo:27017/app?authSource=admin
PORT: 4000
ports:
- "4000:4000"
depends_on:
- mongo
web:
build: ./frontend
environment:
VITE_API_URL: http://localhost:4000
ports:
- "3000:3000"
depends_on:
- api
volumes:
mongodata:
```
---
## 11.10.3 Production Stack
ตัวอย่างสมจริง: Caddy + app + Postgres + Redis
```yaml
services:
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- app
```
---
## Production Stack (App + DB)
```yaml
app:
build: .
restart: unless-stopped
expose:
- "8000"
environment:
DATABASE_URL: postgresql://app:${DB_PASSWORD}@db:5432/app
REDIS_URL: redis://cache:6379/0
depends_on:
db: { condition: service_healthy }
cache: { condition: service_started }
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_DB: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
```
---
## Production Stack (Cache + Caddyfile)
```yaml
cache:
image: redis:7-alpine
command: ["redis-server", "--maxmemory", "256mb",
"--maxmemory-policy", "allkeys-lru"]
volumes:
- redisdata:/data
volumes:
caddy_data:
caddy_config:
pgdata:
redisdata:
```
```
# Caddyfile
example.com {
encode gzip zstd
reverse_proxy app:8000
}
```
---
## 11.10.4 Development Environment
ใช้ Compose สร้าง environment เหมือนทีมทุกคน:
```yaml
# compose.dev.yaml
services:
dev:
image: mcr.microsoft.com/devcontainers/python:3.12
volumes:
- .:/workspaces/project:cached
- vscode-extensions:/root/.vscode-server/extensions
command: sleep infinity
network_mode: service:db
environment:
DATABASE_URL: postgresql://dev:dev@localhost:5432/dev
```
---
## Development Environment (ต่อ)
```yaml
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
POSTGRES_DB: dev
ports:
- "5432:5432"
volumes:
vscode-extensions:
```
ลด "works on my machine" syndrome
---
## 11.10.5 CI/CD Testing Environment
```yaml
# compose.test.yaml – ใช้ใน CI
services:
test:
build:
context: .
target: builder
command: pytest -xvs --cov=app tests/
environment:
DATABASE_URL: postgresql://test:test@db:5432/test
depends_on:
db: { condition: service_healthy }
```
---
## CI Testing (ต่อ)
```yaml
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test
healthcheck:
test: ["CMD-SHELL", "pg_isready -U test"]
interval: 5s
retries: 10
tmpfs:
- /var/lib/postgresql/data # ใน RAM = test เร็ว
```
```bash
docker compose -f compose.test.yaml up \
--abort-on-container-exit --exit-code-from test
```
---
## 11.10.6 Self-hosted Services (1)
```yaml
# Nextcloud + Gitea + Jellyfin
name: home-services
services:
nextcloud:
image: nextcloud:28-apache
restart: unless-stopped
ports:
- "8081:80"
environment:
MYSQL_HOST: nextcloud-db
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: nc_secret
volumes:
- nextcloud_data:/var/www/html
```
---
## Self-hosted Services (2)
```yaml
nextcloud-db:
image: mariadb:11
command: --transaction-isolation=READ-COMMITTED \
--binlog-format=ROW
environment:
MYSQL_ROOT_PASSWORD: rootsecret
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: nc_secret
volumes:
- nextcloud_db:/var/lib/mysql
```
---
## Self-hosted Services (3)
```yaml
gitea:
image: gitea/gitea:1.21
restart: unless-stopped
ports:
- "3000:3000"
- "2222:22"
volumes:
- gitea_data:/data
jellyfin:
image: jellyfin/jellyfin:latest
restart: unless-stopped
ports:
- "8096:8096"
volumes:
- jellyfin_config:/config
- /home/moo/Media:/media:ro
```
---
# 11.11 Security ของ Container
---
## 11.11 Security ของ Container
ความเชื่อผิดที่พบบ่อย: "container แยกกันโดย default ก็ปลอดภัยแล้ว"
ความจริง: container ทุกตัวแบ่ง kernel เดียวกัน ดังนั้นต้องมีการตั้งค่าเสริมหลายชั้น
---
## ชั้นความปลอดภัยของ Container
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3c3836','primaryTextColor':'#ebdbb2','primaryBorderColor':'#fabd2f',
'lineColor':'#fe8019','secondaryColor':'#504945','background':'#282828','mainBkg':'#3c3836'
}}}%%
flowchart TB
L1["1. Image Security
(scan, sign, minimal base)"]
L2["2. Build Security
(non-root, no secret)"]
L3["3. Runtime Security
(read-only, drop caps,
seccomp/AppArmor)"]
L4["4. Network Security
(internal network, firewall)"]
L5["5. Supply Chain
(SBOM, provenance)"]
L1 --> L2 --> L3 --> L4 --> L5
---
## 11.11.1 รันแบบ non-root user
หลัก #1 ที่ควรทำเสมอ — แม้ container แยกจาก host แต่ถ้ารันด้วย root ใน container ก็เพิ่ม attack surface
```dockerfile
# Dockerfile
FROM node:20-alpine
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY --chown=app:app . .
RUN npm ci --omit=dev
USER app
CMD ["node", "server.js"]
```
---
## บังคับ non-root จาก Compose
```yaml
services:
web:
image: myapp
user: "1000:1000"
```
ใช้ในกรณีที่ไม่สามารถแก้ Dockerfile ได้ หรือต้องการ override
---
## 11.11.2 Read-only Root Filesystem
ถ้า application ไม่ต้องเขียน root filesystem ก็ทำให้ read-only เลย:
```yaml
services:
web:
image: myapp
read_only: true
tmpfs:
- /tmp:size=64m
- /var/run:size=8m
volumes:
- logs:/app/logs # เฉพาะที่ต้องเขียน
```
---
## 11.11.3 Image Scanning
```bash
# Trivy
trivy image --severity HIGH,CRITICAL nginx:1.25
# Grype
grype nginx:1.25
# Docker Scout (built-in)
docker scout cves nginx:1.25
# ใส่ใน CI: fail ถ้าพบ critical
trivy image --exit-code 1 \
--severity CRITICAL myapp:1.0
```
---
## 11.11.4 Linux Security Module
```bash
# Custom seccomp profile (JSON)
docker run --security-opt seccomp=./my-seccomp.json myapp
# AppArmor (Ubuntu/Debian)
docker run --security-opt apparmor=docker-default myapp
# disable seccomp (ไม่แนะนำ – เฉพาะ debug)
docker run --security-opt seccomp=unconfined myapp
```
LSM ที่รองรับ: Seccomp, AppArmor, SELinux profile
---
## 11.11.5 Secret Management
**ห้าม** ใส่ secret ใน Dockerfile หรือ environment variable ที่ leak ผ่าน `docker inspect`
```yaml
services:
app:
image: myapp
secrets:
- source: db_password
target: /run/secrets/db_password
mode: 0400
secrets:
db_password:
file: ./secrets/db_password.txt
```
ใน application อ่านจาก `/run/secrets/db_password` แทน env var
---
## 11.11.6 Signed Image: Cosign
ใช้ Cosign (จาก Sigstore) เพื่อเซ็นและตรวจสอบ image:
```bash
# สร้าง keypair
cosign generate-key-pair
# เซ็น image
cosign sign --key cosign.key ghcr.io/moo/myapp:1.0
# ตรวจสอบก่อน pull
cosign verify --key cosign.pub ghcr.io/moo/myapp:1.0
```
ทางเลือก: Docker Content Trust (Notary)
---
## 11.11.7 Supply Chain Security
- **SBOM (Software Bill of Materials)** = รายการ dependency ทั้งหมดของ image
- **Provenance** = หลักฐานว่าใคร/ที่ไหน/จากอะไร build image นี้
```bash
# สร้าง SBOM ด้วย syft
syft ghcr.io/moo/myapp:1.0 -o spdx-json > sbom.json
# Build พร้อม attestation ด้วย buildx
docker buildx build \
--sbom=true \
--provenance=true \
-t ghcr.io/moo/myapp:1.0 \
--push .
```
---
# 11.12 ทางเลือกอื่นและ Ecosystem
---
## 11.12 ทางเลือกอื่น
Docker ไม่ใช่ทางเลือกเดียว — ecosystem ของ container มีเครื่องมือมากมาย:
- Podman, Buildah, Skopeo
- nerdctl + containerd
- LXC / LXD / Incus
- Kubernetes
- Docker Swarm
- Nomad + Consul
- Devcontainer
---
## 11.12.1 Podman
**Podman** เป็น drop-in replacement สำหรับ Docker จาก Red Hat:
- **Daemonless** — ไม่มี dockerd กลาง รัน container ตรง ๆ
- **Rootless by default** — ไม่ต้อง root, ไม่ต้องอยู่ใน group
- **Pod-native** — รองรับ pod แบบ Kubernetes
- **CLI compatible** — `alias docker=podman` ก็ใช้ได้
---
## ตัวอย่างการใช้ Podman
```bash
# ใช้งานเหมือน Docker เลย
podman run -d --name web -p 8080:80 nginx
podman pull alpine
podman build -t myapp .
# สร้าง pod (กลุ่ม container แบ่ง network ร่วมกัน)
podman pod create --name app -p 8080:80
podman run -d --pod app --name web nginx
podman run -d --pod app --name cache redis
```
ทีมเดียวกันมี **Buildah** (build image) และ **Skopeo** (copy image)
---
## 11.12.2 nerdctl + containerd
หากต้องการคุย containerd ตรง ๆ (ไม่ผ่าน Docker) ใช้ **nerdctl**:
```bash
nerdctl run -d --name web -p 8080:80 nginx
nerdctl compose up -d
```
มี syntax เหมือน Docker เลย ทำให้ migration ง่าย
---
## 11.12.3 LXC / LXD / Incus
LXC/LXD/Incus เป็น **system container** — ห่อหุ้มทั้ง init + service ให้เหมือน VM ขนาดเล็ก
ต่างจาก Docker ที่เน้น **application container** (1 process / 1 container)
```bash
# Incus (fork ของ LXD ที่ active)
incus launch images:ubuntu/22.04 c1
incus exec c1 -- bash # เข้า container เหมือน ssh
```
เหมาะกับ: dev environment, CI runner, แทน VM ในงาน infrastructure
---
## 11.12.4 Kubernetes เบื้องต้น
เมื่อ scale เกินเครื่องเดียว Kubernetes (K8s) เป็นมาตรฐาน
| Compose | Kubernetes |
| --- | --- |
| service | Deployment + Pod |
| port mapping | Service |
| environment | ConfigMap, Secret |
| volume | PersistentVolumeClaim |
| depends_on | InitContainer, readinessProbe |
| network | NetworkPolicy |
---
## Compose → K8s ด้วย Kompose
```bash
kompose convert -f compose.yaml -o k8s/
kubectl apply -f k8s/
```
ตัวอย่าง K8s manifest:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels: { app: web }
template:
metadata:
labels: { app: web }
```
---
## K8s Deployment (ต่อ)
```yaml
spec:
containers:
- name: web
image: ghcr.io/moo/myapp:1.0
ports:
- containerPort: 8000
---
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector: { app: web }
ports:
- port: 80
targetPort: 8000
type: ClusterIP
```
---
## 11.12.5 Docker Swarm
Docker Swarm = orchestrator ที่ฝังใน Docker engine
- ใช้ syntax เดียวกับ Compose แต่กระจายไปหลายเครื่องได้
- ติดตั้งง่ายกว่า K8s มาก
- เหมาะกับ small/medium cluster
```bash
# เริ่ม swarm บนเครื่องแรก (manager)
docker swarm init --advertise-addr 192.168.1.10
# เครื่องอื่น join เป็น worker
docker swarm join --token SWMTKN-... 192.168.1.10:2377
```
---
## คำสั่ง Docker Swarm
```bash
# deploy stack จาก compose.yaml ที่มี deploy: section
docker stack deploy -c compose.yaml myapp
# scale service
docker service scale myapp_web=5
# ดูสถานะ
docker service ls
docker service ps myapp_web
docker node ls
```
---
## 11.12.6 Nomad + Consul
**HashiCorp Nomad** = orchestrator ออกแบบให้ง่ายกว่า K8s
- ไม่ผูกอยู่แค่ container
- รัน VM, raw binary, Java, Docker, Podman ได้
- เหมาะกับองค์กรขนาดกลาง
```hcl
job "web" {
group "app" {
count = 3
task "server" {
driver = "docker"
config {
image = "ghcr.io/moo/myapp:1.0"
}
}
}
}
```
---
## 11.12.7 Devcontainer
**Devcontainer** = มาตรฐานจาก Microsoft (VS Code, JetBrains)
บรรยาย environment การพัฒนาในไฟล์ JSON ให้ทุกคนในทีมมี toolchain เหมือนกัน:
```json
{
"name": "Python + PostgreSQL",
"dockerComposeFile": "../compose.dev.yaml",
"service": "dev",
"workspaceFolder": "/workspaces/project",
"postCreateCommand": "pip install -r requirements-dev.txt",
"remoteUser": "vscode"
}
```
---
## Devcontainer Workflow
ผลลัพธ์การใช้ Devcontainer:
1. Clone repo
2. เปิดใน VS Code
3. เลือก "Reopen in Container"
4. ทุก dependency, extension, database พร้อมใช้งานภายในไม่กี่นาที
ลด onboarding time ของสมาชิกใหม่ในทีม
---
## สรุปท้ายบท
Containerization ด้วย Docker และ Docker Compose เป็นเทคโนโลยีฐานที่นักพัฒนาและผู้ดูแลระบบยุคใหม่ต้องเข้าใจ
จุดเริ่มต้นคือเข้าใจว่า:
**Container = namespace + cgroups + UnionFS ของ Linux kernel**
จากนั้นเรียนรู้ Docker เป็นเครื่องมือทำให้ทุกอย่างนั้นใช้ง่าย
---
## เส้นทางการเรียนรู้
1. **เริ่มต้น** — Docker engine + Dockerfile
2. **ระบบหลาย service** — ใช้ Compose ในการรวมศูนย์การตั้งค่า
3. **ระบบโตข้ามเครื่อง** — ขยายไปยัง Swarm, Nomad หรือ Kubernetes
---
## ข้อสำคัญที่ห้ามลืม
**ความปลอดภัย** เป็นเรื่องที่ห้ามมองข้าม:
- รัน non-root user
- Scan image อย่างสม่ำเสมอ
- จัดการ secret ให้ถูกต้อง
- ตระหนักเสมอว่า container ใช้ kernel เดียวกับ host
---
# คําถาม - ข้อสงสัย