วันแรกที่เจอ Docker
ยังจำได้เลยตอนที่เพื่อนแนะนำ Docker มาให้ครั้งแรก พอได้ยิน “Container” กับ “Image” ก็แอบคิดในใจว่า “อะไรเนี่ย ซับซ้อนจัง แค่รันโปรแกรมเองก็ได้แล้ว” 😅
แต่พอเวลาผ่านไปสักพัก แล้วมาเจอปัญหาคลาสสิคที่ทุกคนต้องเจอ:
“ในเครื่องผมทำงานนะ แต่ทำไมไปแล้วไม่ทำงานเลย?”
ตอนนั้นแหละที่เริ่มเข้าใจว่า Docker มันมีไว้แก้ปัญหาอะไร
ประสบการณ์แรกที่ใช้ Docker
โปรเจคแรกที่ใช้ Docker เป็น Node.js app ง่ายๆ เขียน Dockerfile แบบนี้:
# Dockerfile แรกของผม (ไม่ดีเลย)
FROM node:latest
COPY . /app
WORKDIR /app
RUN npm install
CMD ["npm", "start"]
ตอนนั้นคิดว่าเขียนได้แล้ว แต่พอเอาไป build มัน error ไม่รู้กี่ทีเพราะ:
- ใช้
node:latest(ไม่ควรใช้ latest ในการทำงานจริง) - Copy ทุกอย่างรวม
node_modulesด้วย - ไม่มี
.dockerignore
บทเรียนจากความผิดพลาด
1. อย่าใช้ latest tags
# ❌ ไม่ดี - ไม่รู้ได้ Node version ไหน
FROM node:latest
# ✅ ดีกว่า - ระบุ version ชัดเจน
FROM node:18-alpine
เหตุผลง่ายๆ คือถ้าใช้ latest วันนี้อาจจะเป็น Node 18 แต่พรุ่งนี้อาจจะเป็น Node 20 แล้วโค้ดเราอาจจะไม่ทำงาน
2. ใช้ .dockerignore
สร้างไฟล์ .dockerignore เพื่อไม่ให้ copy ของที่ไม่จำเป็น:
node_modules
npm-debug.log
.git
.env
*.md
.DS_Store
ตอนแรกผมไม่รู้เรื่องนี้ เลยมี Docker image ขนาด 2GB เพราะ copy node_modules ขนาด 500MB ไปด้วย 🤦♂️
3. Multi-stage builds คือเทพ
หลังจากใช้ Docker ได้สักพัก เริ่มเรียนรู้เรื่อง Multi-stage builds:
# Stage 1: Build dependencies
FROM node:18-alpine AS dependencies
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Stage 2: Build application
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 3: Production image
FROM node:18-alpine AS production
WORKDIR /app
# Copy only production dependencies
COPY /app/node_modules ./node_modules
# Copy built application
COPY /app/dist ./dist
COPY /app/package*.json ./
EXPOSE 3000
CMD ["npm", "start"]
ทำแบบนี้ได้ image ขนาดเล็กลง เพราะไม่เอา dev dependencies ไปด้วย
เคสที่น่าจำ: Database Connection
เคสที่จำได้ชัดที่สุดคือตอนที่แอพใน Docker ติดต่อ database ไม่ได้ สงสัยมากว่าทำไม localhost:5432 เข้าไม่ได้
ปรากฎว่า localhost ใน container มันคือ localhost ของ container นั้น ไม่ใช่ของเครื่องเรา!
วิธีแก้:
1. ใช้ host.docker.internal (บน Windows/Mac)
// แทนที่จะใช้
const dbUrl = 'postgresql://localhost:5432/mydb';
// ให้ใช้
const dbUrl = 'postgresql://host.docker.internal:5432/mydb';
2. ใช้ Docker Compose (แนะนำ)
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/mydb
depends_on:
- db
db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=mydb
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
สังเกตว่าใน DATABASE_URL ใช้ db:5432 ไม่ใช่ localhost:5432 เพราะ db คือชื่อ service ใน Docker Compose
เทคนิคที่ช่วยประหยัดเวลา
1. Layer Caching
Docker มี layer caching ที่เจ๋งมาก ถ้าเรียงลำดับ Dockerfile ให้ดี:
FROM node:18-alpine
WORKDIR /app
# Copy package.json ก่อน (ไม่ค่อยเปลี่ยน)
COPY package*.json ./
RUN npm ci
# Copy source code ทีหลัง (เปลี่ยนบ่อย)
COPY . .
CMD ["npm", "start"]
ทำแบบนี้ ถ้าเปลี่ยนแค่ source code จะไม่ต้อง npm install ใหม่
2. ใช้ .env กับ Docker Compose
# docker-compose.yml
services:
app:
build: .
env_file:
- .env
ports:
- "${PORT:-3000}:3000"
# .env
PORT=8080
DATABASE_URL=postgresql://localhost:5432/mydb
NODE_ENV=development
3. Health Checks
เพิ่ม health check เข้าไปใน Dockerfile:
HEALTHCHECK \
CMD curl -f http://localhost:3000/health || exit 1
หรือใน Docker Compose:
services:
app:
build: .
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
ข้อผิดพลาดที่เจอบ่อยๆ
1. Permission Issues
บน Linux เจอปัญหาไฟล์ที่สร้างใน container เป็น root:
# แก้โดยสร้าง user ใหม่
FROM node:18-alpine
# สร้าง user สำหรับรันแอพ
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs
WORKDIR /app
COPY . .
CMD ["npm", "start"]
2. Time Zone Issues
# ตั้ง timezone
FROM node:18-alpine
RUN apk add --no-cache tzdata
ENV TZ=Asia/Bangkok
WORKDIR /app
# ... rest of Dockerfile
3. Memory Limits
ตอนแรกไม่รู้ เลย build แล้วกิน RAM เต็มเครื่อง:
# docker-compose.yml
services:
app:
build: .
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
Docker ในการทำงานจริง
Development Environment
ใช้ Docker Compose เพื่อให้ทีมทำงานใน environment เดียวกัน:
# docker-compose.dev.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/app
- /app/node_modules
ports:
- "3000:3000"
environment:
- NODE_ENV=development
depends_on:
- db
- redis
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp_dev
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev123
ports:
- "5432:5432"
volumes:
- postgres_dev:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
postgres_dev:
Production Deployment
สำหรับ production ใช้ multi-stage build:
# Dockerfile.prod
FROM node:18-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine AS runtime
WORKDIR /app
# Security: Don't run as root
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodeuser -u 1001
COPY /app/node_modules ./node_modules
COPY /app/dist ./dist
COPY /app/package.json ./
USER nodeuser
EXPOSE 3000
CMD ["node", "dist/index.js"]
เทคนิคขั้นสูงที่ใช้จริง
1. Build Arguments
ARG NODE_VERSION=18
FROM node:${NODE_VERSION}-alpine
ARG BUILD_DATE
ARG VERSION
LABEL build_date=$BUILD_DATE
LABEL version=$VERSION
docker build \
--build-arg NODE_VERSION=20 \
--build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
--build-arg VERSION=1.0.0 \
-t myapp:1.0.0 .
2. Multi-platform builds
FROM node:18-alpine AS base
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest .
3. การ optimize image size
FROM node:18-alpine
# รวม RUN commands เพื่อลด layers
RUN apk add --no-cache \
dumb-init \
&& npm install -g pm2 \
&& addgroup -g 1001 -S nodejs \
&& adduser -S nodeuser -u 1001
# ใช้ dumb-init สำหรับ signal handling
ENTRYPOINT ["dumb-init", "--"]
USER nodeuser
WORKDIR /app
CMD ["pm2-runtime", "start", "ecosystem.config.js"]
Docker ช่วยแก้ปัญหาอะไรบ้าง
1. “ในเครื่องผมทำงานนะ”
ก่อนใช้ Docker:
- Dev: “ใช้ Node 16 นะ”
- QA: “เอ๊ะ ผมใช้ Node 14 ทำไมไม่ทำงาน”
- DevOps: “เซิร์ฟเวอร์ใช้ Node 12…”
หลังใช้ Docker: ทุกคนใช้ environment เดียวกัน ✨
2. Dependency Hell
ก่อน:
npm install
# Error: Python 2.7 required
# Error: node-gyp failed
# Error: sqlite3 compilation failed
หลัง: ทุกอย่างอยู่ใน container แล้ว
3. Microservices Development
ก่อน: ต้องเปิด 5 terminals รัน 5 services
# Terminal 1
cd user-service && npm start
# Terminal 2
cd order-service && npm start
# Terminal 3
cd payment-service && npm start
# Terminal 4
cd notification-service && npm start
# Terminal 5
cd api-gateway && npm start
หลัง:
docker-compose up
เสร็จ! 🎉
เครื่องมือที่ช่วยให้ชีวิตง่ายขึ้น
1. Docker Desktop
UI ที่ใช้งานง่าย ดูได้ว่า container ไหนรันอยู่ ใช้ resource เท่าไหร่
2. Portainer
Web UI สำหรับจัดการ Docker ใช้ดีมาก:
docker run -d \
--name portainer \
-p 9000:9000 \
-v /var/run/docker.sock:/var/run/docker.sock \
portainer/portainer-ce
3. Dive
เช็คว่า Docker image มี layer อะไรบ้าง ใหญ่แค่ไหน:
dive myapp:latest
4. Hadolint
ตรวจสอบ Dockerfile ว่าเขียนถูกต้องไหม:
hadolint Dockerfile
สรุปประสบการณ์
Docker มันเปลี่ยนวิธีทำงานของผมไปเลย จากที่เคยมีปัญหา:
- Environment ไม่เหมือนกัน → ตอนนี้ทุกคนใช้ environment เดียวกัน
- Deploy ยาก → ตอนนี้ build 1 ที รัน ได้ทุกที่
- Scale ยาก → ตอนนี้เพิ่ม container ได้เท่าที่ต้องการ
แต่ก็ต้องเรียนรู้เรื่อง:
- Container orchestration (Kubernetes)
- Security (ไม่รัน container เป็น root)
- Monitoring (ดู logs, metrics)
- Storage (volumes, bind mounts)
Docker มันเหมือนมีดในครัว ถ้าใช้เป็น มันช่วยได้เยอะมาก แต่ถ้าใช้ไม่เป็น อาจจะเจ็บตัวเองได้ 😅
สำหรับคนที่กำลังเริ่มต้น แนะนำให้ ลองเล่นก่อน แล้วค่อยๆ เรียนรู้ อย่าเอาไปใช้ใน production ตั้งแต่วันแรก เพราะมันมีหลุมพรางเยอะ
แต่พอเรียนรู้แล้ว การันตีได้เลยว่า ชีวิตจะง่ายขึ้นมาก! 🐳