หลายคนที่เริ่มทำ Home Lab มักเจอปัญหาเดิม คือรัน Docker container ด้วย command ยาวๆ แล้วลืมว่าพิมพ์อะไรไป พอ container พังหรือต้อง restart server ก็ต้องมานั่งไล่ดูว่า flag อะไรที่ใส่ไป Docker Compose แก้ปัญหานี้ได้ตรงๆ ด้วยการเอา configuration ทุกอย่างไปเขียนในไฟล์เดียว
บทความนี้จะพาทำ Home Lab ด้วย Docker Compose ตั้งแต่พื้นฐานจนถึง stack ที่ใช้งานได้จริง รวมถึง service ยอดนิยมอย่าง Portainer, Nginx Proxy Manager, Pi-hole, Nextcloud และ Grafana+Prometheus
Docker Compose คืออะไร และทำไมต้องใช้
Docker Compose เป็นเครื่องมือที่ช่วยจัดการ multi-container Docker application ด้วยไฟล์ YAML ไฟล์เดียว แทนที่จะต้องรัน docker run หลายๆ ครั้งพร้อม option เยอะๆ ก็เขียนทุกอย่างลงใน docker-compose.yml แล้วรันแค่ docker compose up -d
ข้อดีหลักๆ ที่ทำให้ใช้ Docker Compose ใน Home Lab
- Reproducible — ย้าย config ไป server ใหม่ได้ทันที
- Version control — เก็บไฟล์ใน Git ได้
- Dependency management — กำหนดลำดับการ start service ได้
- Network isolation — แต่ละ stack มี network ของตัวเอง
ปัจจุบัน Docker Compose v2 เป็น plugin ในตัว Docker แล้ว ไม่ต้องติดตั้งแยก และใช้คำสั่ง docker compose แทน docker-compose (ไม่มี hyphen)
โครงสร้าง docker-compose.yml
ก่อนจะลงมือทำ Home Lab ต้องเข้าใจโครงสร้างพื้นฐานของไฟล์ก่อน
services:
ชื่อ-service:
image: image-name:tag
container_name: ชื่อ-container
restart: unless-stopped
ports:
- "host-port:container-port"
volumes:
- /path/on/host:/path/in/container
environment:
- VARIABLE_NAME=value
networks:
- ชื่อ-network
depends_on:
- service-อื่น
networks:
ชื่อ-network:
driver: bridge
volumes:
ชื่อ-volume:
driver: local
Key concepts ที่ต้องรู้:
- services — รายการ container ที่จะรัน
- image — Docker image ที่ใช้
- restart: unless-stopped — restart อัตโนมัติ ยกเว้นกรณีที่ stop เอง
- volumes — mount directory จาก host เข้า container เพื่อให้ข้อมูลอยู่รอด
- networks — กำหนด network ที่ container ใช้
Environment Variables และ .env File
อย่าเขียน password หรือ secret ตรงๆ ใน docker-compose.yml สร้าง .env file แยกแล้วอ้างอิงแทน
# .env file
POSTGRES_PASSWORD=your-super-secret-password
DOMAIN=yourdomain.com
TZ=Asia/Bangkok
PUID=1000
PGID=1000
# docker-compose.yml
services:
postgres:
image: postgres:16
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- TZ=${TZ}
เพิ่ม .env เข้า .gitignore เสมอ แต่เก็บ .env.example ไว้ใน repo เพื่อให้คนอื่นรู้ว่าต้องกำหนด variable อะไรบ้าง
Service หลักสำหรับ Home Lab
1. Portainer — จัดการ Docker ผ่าน Web UI
Portainer เป็น GUI สำหรับจัดการ Docker ที่ใช้งานง่ายที่สุด เหมาะสำหรับคนที่ไม่อยากพิมพ์ command ตลอดเวลา
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
ports:
- "9000:9000"
- "9443:9443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
volumes:
portainer_data:
หลังรันแล้วเข้าไปที่ http://server-ip:9000 สร้าง admin account แล้วก็จัดการ container ผ่าน web ได้เลย
2. Nginx Proxy Manager — Reverse Proxy พร้อม SSL
แทนที่จะเปิด port ตรงๆ ให้ทุก service ใช้ Nginx Proxy Manager (NPM) เป็น reverse proxy แล้วจัดการ SSL certificate ได้ง่ายๆ ผ่าน GUI
services:
npm:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "81:81" # Admin UI
volumes:
- npm_data:/data
- npm_letsencrypt:/etc/letsencrypt
environment:
- TZ=${TZ}
volumes:
npm_data:
npm_letsencrypt:
Port 81 คือ Admin UI เข้าครั้งแรกใช้ admin@example.com / changeme แล้วเปลี่ยนทันที NPM จะ request SSL certificate จาก Let's Encrypt ให้อัตโนมัติ
3. Pi-hole — Block Ads ทั้งบ้าน
Pi-hole ทำหน้าที่เป็น DNS server ที่ block domain ของ ad network ทำให้ทุก device ในบ้านที่ใช้ DNS นี้ไม่เห็นโฆษณา
services:
pihole:
image: pihole/pihole:latest
container_name: pihole
restart: unless-stopped
ports:
- "53:53/tcp"
- "53:53/udp"
- "8080:80/tcp"
environment:
- TZ=${TZ}
- WEBPASSWORD=${PIHOLE_PASSWORD}
volumes:
- pihole_etc:/etc/pihole
- pihole_dnsmasq:/etc/dnsmasq.d
cap_add:
- NET_ADMIN
volumes:
pihole_etc:
pihole_dnsmasq:
สิ่งสำคัญ: ต้อง disable systemd-resolved บน Ubuntu ก่อนเพราะชน port 53
sudo systemctl disable systemd-resolved
sudo systemctl stop systemd-resolved
sudo rm /etc/resolv.conf
echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf
4. Nextcloud — Private Cloud Storage
Nextcloud เป็น self-hosted alternative สำหรับ Google Drive / iCloud ใช้ PostgreSQL เป็น database และ Redis สำหรับ caching
services:
nextcloud-db:
image: postgres:16
container_name: nextcloud-db
restart: unless-stopped
volumes:
- nextcloud_db:/var/lib/postgresql/data
environment:
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud
- POSTGRES_PASSWORD=${NEXTCLOUD_DB_PASSWORD}
nextcloud-redis:
image: redis:alpine
container_name: nextcloud-redis
restart: unless-stopped
nextcloud:
image: nextcloud:latest
container_name: nextcloud
restart: unless-stopped
ports:
- "8081:80"
volumes:
- nextcloud_data:/var/www/html
environment:
- POSTGRES_HOST=nextcloud-db
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud
- POSTGRES_PASSWORD=${NEXTCLOUD_DB_PASSWORD}
- REDIS_HOST=nextcloud-redis
- NEXTCLOUD_TRUSTED_DOMAINS=cloud.${DOMAIN}
- TZ=${TZ}
depends_on:
- nextcloud-db
- nextcloud-redis
networks:
- nextcloud_net
- default
networks:
nextcloud_net:
volumes:
nextcloud_db:
nextcloud_data:
5. Grafana + Prometheus — Monitoring Stack
ดู metrics ของ server และ service ต่างๆ แบบ real-time ด้วย Grafana dashboard และ Prometheus เป็น data source
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
- GF_USERS_ALLOW_SIGN_UP=false
depends_on:
- prometheus
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|host|etc)($$|/)'
volumes:
prometheus_data:
grafana_data:
สร้าง prometheus.yml ด้วย:
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
การจัดการ Network
Docker Compose สร้าง network ให้อัตโนมัติชื่อ projectname_default ทุก service ใน compose file เดียวกันคุยกันได้ผ่านชื่อ service แต่ถ้าต้องการให้ service คนละ stack คุยกัน ต้องสร้าง external network
# สร้าง network สำหรับ proxy
docker network create proxy
# ใน compose file ของ NPM
networks:
proxy:
external: true
# ใน compose file ของ service อื่น
networks:
proxy:
external: true
วิธีนี้ทำให้ NPM เข้าถึง service อื่นๆ ได้โดยไม่ต้อง expose port ออกมา
คำสั่งที่ใช้บ่อย
# Start ทุก service
docker compose up -d
# Stop ทุก service
docker compose down
# ดู logs
docker compose logs -f
docker compose logs -f service-name
# Restart service เดียว
docker compose restart service-name
# Pull image ใหม่และ recreate
docker compose pull && docker compose up -d
# ดูสถานะ service
docker compose ps
# เข้าไปใน container
docker compose exec service-name bash
Tips การจัดการ Stack หลายอัน
เมื่อ service เยอะขึ้น ควรแยก compose file ตาม category
~/docker/
├── infrastructure/
│ ├── docker-compose.yml # NPM, Portainer
│ └── .env
├── media/
│ ├── docker-compose.yml # Jellyfin, Radarr, etc.
│ └── .env
├── monitoring/
│ ├── docker-compose.yml # Grafana, Prometheus
│ ├── prometheus.yml
│ └── .env
└── services/
├── docker-compose.yml # Nextcloud, Pi-hole
└── .env
สร้าง script สำหรับ update ทุก stack พร้อมกัน
#!/bin/bash
STACKS=("infrastructure" "media" "monitoring" "services")
for stack in "${STACKS[@]}"; do
echo "Updating $stack..."
cd ~/docker/$stack
docker compose pull
docker compose up -d
done
echo "All stacks updated!"
สำหรับ Home Lab ขั้นสูง ลองดูบทความ การ Setup Proxmox Cluster เพื่อรัน Docker บน VM หลายๆ เครื่องได้ และ Cloudflare Tunnel สำหรับ expose service ออก internet อย่างปลอดภัยโดยไม่ต้อง port forward
การ Backup Volumes
ข้อมูลสำคัญอยู่ใน Docker volumes ต้อง backup สม่ำเสมอ
# Backup volume เป็น tar
docker run --rm \
-v volume-name:/data \
-v /backup:/backup \
alpine tar czf /backup/volume-name-$(date +%Y%m%d).tar.gz -C /data .
# Restore จาก backup
docker run --rm \
-v volume-name:/data \
-v /backup:/backup \
alpine tar xzf /backup/volume-name-20260101.tar.gz -C /data
หรือจะใช้ แนวทางการรักษาความปลอดภัย server ร่วมด้วยเพื่อให้ Home Lab มีความปลอดภัยมากขึ้น
คำถามที่พบบ่อย (FAQ)
Docker Compose v2 ต่างจาก v1 อย่างไร?
Docker Compose v2 เป็น Go binary ที่ build ใหม่ทั้งหมด เร็วกว่า v1 ที่เป็น Python script และรวมเป็น plugin ของ Docker แล้ว ใช้คำสั่ง docker compose แทน docker-compose v1 ถูก deprecate แล้วตั้งแต่ปี 2023
ควรใช้ named volumes หรือ bind mounts?
Named volumes (volume-name:/path) เหมาะสำหรับ database และข้อมูลที่ Docker จัดการเอง Bind mounts (/host/path:/container/path) เหมาะสำหรับ config files ที่ต้องการแก้ไขง่ายจาก host
ทำไม container restart ตัวเองตลอด?
ตรวจสอบ logs ด้วย docker compose logs service-name สาเหตุทั่วไปคือ config ผิด, environment variable ขาด, หรือ port ชนกัน
จะ update image ทุก container พร้อมกันได้ไหม?
ได้ด้วยคำสั่ง docker compose pull && docker compose up -d Docker จะ pull image ใหม่และ recreate เฉพาะ container ที่ image เปลี่ยน
Home Lab ต้องใช้ RAM เท่าไหร่?
stack พื้นฐาน (Portainer + NPM + Pi-hole) ใช้ประมาณ 512MB เพิ่ม Nextcloud อีกประมาณ 1GB และ monitoring stack อีก 512MB รวมๆ สำหรับทุก service ในบทความนี้ควรมี RAM อย่างน้อย 4GB
จะเก็บ docker-compose.yml ใน Git ได้ไหม?
ได้ และควรทำ แต่อย่าลืมเพิ่ม .env เข้า .gitignore เก็บแค่ .env.example ไว้เป็น template
