
Docker Security คืออะไร? ทำไมต้องใส่ใจ
Docker Security คือแนวทางปฏิบัติในการรักษาความปลอดภัยของ Container, Docker Image, Docker Daemon และ Infrastructure ที่เกี่ยวข้องทั้งหมด ในปี 2026 องค์กรส่วนใหญ่ใช้ Docker ใน Production แล้ว แต่หลายองค์กรยังขาดการดูแลเรื่อง Security ทำให้เสี่ยงต่อการถูก โจมตี ผ่านช่องโหว่ใน Container
การใช้ Docker โดยไม่คำนึงถึง Security เหมือนกับการสร้างบ้านที่แข็งแรง แต่ลืมล็อคประตู ผู้โจมตีสามารถเจาะเข้าระบบผ่าน Container ที่ไม่ปลอดภัย และอาจ Escape ออกจาก Container เข้าถึง Host system ได้
Docker Attack Surface — พื้นผิวการโจมตี
ก่อนจะป้องกันได้ ต้องเข้าใจก่อนว่า ผู้โจมตีเข้ามาทางไหนได้บ้าง:
| Attack Surface | คำอธิบาย | ความเสี่ยง |
|---|---|---|
| Docker Image | Image ที่มี Vulnerability, Malware, หรือ Hardcoded secrets | สูงมาก — Image จาก Docker Hub อาจมีช่องโหว่หลายร้อยรายการ |
| Docker Daemon | Docker Daemon ทำงานเป็น root ถ้าถูกเข้าถึงจะ Control ทุก Container | สูงมาก — เท่ากับ root access ทั้ง Host |
| Docker Socket | /var/run/docker.sock ถ้า Mount เข้า Container = Container นั้นคุม Docker ทั้งหมด | สูงมาก — Container escape ง่ายมาก |
| Container Runtime | ช่องโหว่ใน containerd หรือ runc ทำให้ Escape ออกจาก Container ได้ | สูง — เกิดขึ้นจริง (CVE-2024-21626) |
| Network | Container ที่เข้าถึง Network ได้ไม่จำกัด สามารถ Lateral movement ไปยัง Container อื่น | ปานกลาง-สูง |
| Host OS | Kernel ที่ Share กับ Container ถ้ามีช่องโหว่ Container อาจ Exploit ได้ | สูง |
Minimal Base Images — เลือก Base Image ที่เล็กที่สุด
หลักการสำคัญ: ยิ่ง Image เล็ก ยิ่งมี Attack surface น้อย ไม่ต้องมี Shell ไม่ต้องมี Package manager ยิ่งดี
เปรียบเทียบ Base Images
| Base Image | ขนาด | Packages | Shell | เหมาะกับ |
|---|---|---|---|---|
| ubuntu:24.04 | ~78 MB | มาก (apt) | bash, sh | Development, ระบบที่ต้อง Debug |
| debian:bookworm-slim | ~52 MB | ปานกลาง | bash, sh | Production ที่ต้องการ Compatibility |
| alpine:3.20 | ~7 MB | น้อย (apk) | sh (busybox) | Production ทั่วไป |
| distroless | ~3-20 MB | แทบไม่มี | ไม่มี | Production ที่ Security สำคัญมาก |
| scratch | 0 MB | ไม่มีเลย | ไม่มี | Static binary (Go, Rust) |
# ตัวอย่าง: Go app ใน scratch (เล็กที่สุด)
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server .
FROM scratch
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/server"]
# ตัวอย่าง: Node.js app ใน distroless
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
FROM gcr.io/distroless/nodejs20-debian12
COPY --from=builder /app /app
WORKDIR /app
CMD ["server.js"]
Non-Root Containers — ห้ามรัน Container เป็น root
ปัญหาใหญ่ที่สุดที่พบบ่อย: Container ส่วนใหญ่รันเป็น root โดยค่าเริ่มต้น ถ้าผู้โจมตีเข้ายึด Process ใน Container ได้ ก็จะได้สิทธิ์ root ซึ่งอาจ Escape ออกจาก Container ได้
# Dockerfile: สร้าง User แยก
FROM node:20-alpine
# สร้าง User ที่ไม่ใช่ root
RUN addgroup -g 1001 -S appgroup && adduser -u 1001 -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
RUN npm ci --production
# สลับไปใช้ User ที่ไม่ใช่ root
USER appuser
EXPOSE 3000
CMD ["node", "server.js"]
# ตรวจสอบว่ารันเป็น root หรือไม่:
# docker exec container_name whoami
# ถ้าได้ "root" = ไม่ปลอดภัย!
Kubernetes: SecurityContext
# ใน Kubernetes ใช้ SecurityContext บังคับ
apiVersion: v1
kind: Pod
spec:
securityContext:
runAsNonRoot: true # ห้ามรันเป็น root
runAsUser: 1001 # UID ที่ใช้
runAsGroup: 1001 # GID ที่ใช้
fsGroup: 1001 # Group สำหรับ Volume
seccompProfile:
type: RuntimeDefault # ใช้ Seccomp profile
containers:
- name: app
image: myapp:v1
securityContext:
allowPrivilegeEscalation: false # ห้าม Privilege escalation
readOnlyRootFilesystem: true # Filesystem อ่านอย่างเดียว
capabilities:
drop: ["ALL"] # ลบ Linux capabilities ทั้งหมด
Read-Only Filesystem — ล็อค Filesystem
การทำ Filesystem เป็น Read-only ป้องกันไม่ให้ผู้โจมตีเขียนไฟล์ลง Container ได้ (เช่น Web shell, Malware, Crypto miner):
# Docker run กับ Read-only filesystem
docker run --read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m --tmpfs /var/run:rw,noexec,nosuid,size=16m myapp:v1
# Docker Compose
services:
app:
image: myapp:v1
read_only: true
tmpfs:
- /tmp:size=64m
- /var/run:size=16m
# เฉพาะ /tmp และ /var/run เท่านั้นที่เขียนได้
# ทุก path อื่นเขียนไม่ได้ — ป้องกัน Malware
Resource Limits — จำกัดทรัพยากร
ถ้าไม่กำหนด Resource limits Container สามารถกิน CPU และ Memory ของ Host ทั้งหมดได้ ทำให้ Container อื่นๆ หรือ Host เอง หยุดทำงาน (Denial of Service):
# Docker run กับ Resource limits
docker run -d --memory=512m --memory-swap=512m --cpus=1.0 --pids-limit=100 --restart=unless-stopped myapp:v1
# อธิบาย:
# --memory=512m → ใช้ RAM ได้สูงสุด 512 MB
# --memory-swap=512m → ไม่ใช้ Swap (เท่ากับ memory)
# --cpus=1.0 → ใช้ CPU ได้สูงสุด 1 core
# --pids-limit=100 → จำกัด Process ได้ไม่เกิน 100 (ป้องกัน Fork bomb)
# Docker Compose
services:
app:
image: myapp:v1
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
Docker Secrets — จัดการ Secrets อย่างปลอดภัย
หนึ่งในข้อผิดพลาดที่พบบ่อยที่สุดคือ ใส่ Secret ใน ENV ของ Dockerfile ซึ่งจะถูกบันทึกใน Image layer ใครก็ตามที่มี Image สามารถดู Secret ได้:
# ผิด! Secret อยู่ใน Image layer ตลอดไป
ENV DATABASE_PASSWORD=supersecret123
ENV API_KEY=sk-1234567890abcdef
# วิธีที่ถูก: ใช้ Docker Secrets (Swarm mode)
echo "supersecret123" | docker secret create db_password -
# docker-compose.yml กับ Secrets
version: '3.8'
services:
app:
image: myapp:v1
secrets:
- db_password
- api_key
environment:
DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txt # ไม่เก็บใน Image
api_key:
external: true # สร้างไว้แล้ว
# ใน Application อ่านจากไฟล์:
# const password = fs.readFileSync('/run/secrets/db_password', 'utf8').trim();
# วิธีอื่น: ใช้ .env file (ไม่ Commit เข้า Git)
# docker run --env-file .env myapp:v1
Docker Socket Protection — ปกป้อง Docker Socket
/var/run/docker.sock คือ API endpoint ของ Docker daemon ถ้า Mount เข้า Container ใด Container นั้นจะ ควบคุม Docker ทั้ง Host ได้ เท่ากับ root access:
# อันตราย! ห้ามทำ (ยกเว้นจำเป็นจริงๆ)
docker run -v /var/run/docker.sock:/var/run/docker.sock myapp:v1
# ถ้า Container ถูก Compromise ผู้โจมตีสามารถ:
# 1. สร้าง Container ใหม่ที่ Mount Host filesystem
# 2. docker run -v /:/host --privileged attacker-image
# 3. เข้าถึงไฟล์ทั้งหมดบน Host
# 4. Control ทุก Container
# ถ้าจำเป็นต้องใช้ Docker socket (เช่น Portainer, Traefik):
# 1. ใช้ Docker Socket Proxy (tecnativa/docker-socket-proxy)
# 2. จำกัด API ที่เข้าถึงได้
services:
socket-proxy:
image: tecnativa/docker-socket-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
CONTAINERS: 1 # อนุญาตดู Containers
SERVICES: 0 # ไม่อนุญาต Services
NETWORKS: 0 # ไม่อนุญาต Networks
VOLUMES: 0 # ไม่อนุญาต Volumes
EXEC: 0 # ไม่อนุญาต Exec (สำคัญมาก!)
POST: 0 # ไม่อนุญาต POST requests
traefik:
image: traefik:v3
depends_on:
- socket-proxy
environment:
DOCKER_HOST: tcp://socket-proxy:2375 # เชื่อมผ่าน Proxy แทน
Docker Bench for Security — ตรวจสอบตาม CIS Benchmark
Docker Bench for Security เป็นเครื่องมือจาก Docker ที่ตรวจสอบ Configuration ตาม CIS Docker Benchmark ซึ่งเป็นมาตรฐานสากลสำหรับ Docker Security:
# รัน Docker Bench for Security
docker run --rm --net host --pid host --userns host --cap-add audit_control -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST -v /etc:/etc:ro -v /usr/bin/containerd:/usr/bin/containerd:ro -v /usr/bin/runc:/usr/bin/runc:ro -v /usr/lib/systemd:/usr/lib/systemd:ro -v /var/lib:/var/lib:ro -v /var/run/docker.sock:/var/run/docker.sock:ro docker/docker-bench-security
# ผลลัพธ์จะแบ่งเป็นหมวด:
# [PASS] 1.1 - Ensure a separate partition for containers has been created
# [WARN] 2.1 - Ensure network traffic is restricted between containers
# [FAIL] 4.1 - Ensure that a user for the container has been created
# [INFO] 5.1 - Ensure AppArmor Profile is set
# หมวดที่ตรวจ:
# 1. Host Configuration
# 2. Docker Daemon Configuration
# 3. Docker Daemon Configuration Files
# 4. Container Images and Build File
# 5. Container Runtime Configuration
# 6. Docker Security Operations
# 7. Docker Swarm Configuration
Dockerfile Best Practices — แนวทางเขียน Dockerfile ที่ปลอดภัย
Multi-Stage Builds
ใช้ Multi-stage build เพื่อแยก Build tools ออกจาก Production image ลดขนาด Image และลด Attack surface:
# Stage 1: Build (มี tools ทั้งหมด)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN npm prune --production # เอา devDependencies ออก
# Stage 2: Production (แค่ Runtime)
FROM node:20-alpine AS production
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
# Copy เฉพาะที่จำเป็น
COPY --from=builder --chown=app:app /app/dist ./dist
COPY --from=builder --chown=app:app /app/node_modules ./node_modules
COPY --from=builder --chown=app:app /app/package.json ./
USER app
EXPOSE 3000
CMD ["node", "dist/server.js"]
Pin Image Versions
# ผิด! ใช้ latest tag — ไม่รู้ว่า Image เปลี่ยนเมื่อไหร่
FROM node:latest
FROM nginx:latest
# ถูก! Pin version + SHA256 digest
FROM node:20.12-alpine3.19@sha256:abcdef1234567890...
FROM nginx:1.25-alpine@sha256:fedcba0987654321...
# Pin version ช่วย:
# 1. Reproducible builds — Build ซ้ำได้ผลเหมือนเดิม
# 2. ป้องกัน Supply chain attack — Image ไม่ถูกสลับ
# 3. ควบคุม Update — Update เมื่อพร้อม ไม่ใช่อัตโนมัติ
Image Scanning ใน CI — ตรวจช่องโหว่อัตโนมัติ
ต้องสแกน Image ทุกครั้งก่อน Push ไป Registry หรือ Deploy ไป Production เครื่องมือยอดนิยมในปี 2026:
| เครื่องมือ | ประเภท | จุดเด่น | ราคา |
|---|---|---|---|
| Trivy | Open-source (Aqua Security) | เร็ว, ครอบคลุม, สแกนได้ทั้ง Image/Filesystem/IaC | ฟรี |
| Grype | Open-source (Anchore) | เร็ว, Database อัพเดทบ่อย, SBOM support | ฟรี |
| Snyk Container | SaaS + CLI | แนะนำ Fix, Developer-friendly | Free tier + Paid |
| Docker Scout | Docker Official | Integration กับ Docker Desktop, Docker Hub | Free tier + Paid |
# Trivy — สแกน Image
trivy image myapp:v1
# Trivy — สแกนเฉพาะ Critical/High
trivy image --severity CRITICAL,HIGH myapp:v1
# Trivy — สแกน Dockerfile
trivy config Dockerfile
# Grype — สแกน Image
grype myapp:v1
# GitHub Actions: สแกน Image อัตโนมัติ
# .github/workflows/scan.yml
name: Container Scan
on: push
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Image
run: docker build -t myapp:${{ github.sha }} .
- name: Trivy Scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: CRITICAL,HIGH
exit-code: 1 # Fail CI ถ้าพบ Critical/High
Signed Images — Docker Content Trust & Cosign
การ Sign image ช่วยยืนยันว่า Image มาจาก แหล่งที่เชื่อถือได้ และไม่ถูกแก้ไขระหว่างทาง:
# Docker Content Trust (DCT)
export DOCKER_CONTENT_TRUST=1
docker push myregistry.com/myapp:v1 # จะ Sign อัตโนมัติ
docker pull myregistry.com/myapp:v1 # จะ Verify อัตโนมัติ
# Cosign (Sigstore) — ยอดนิยมในปี 2026
# ติดตั้ง: go install github.com/sigstore/cosign/v2/cmd/cosign@latest
# Sign image
cosign sign --key cosign.key myregistry.com/myapp:v1
# Verify image
cosign verify --key cosign.pub myregistry.com/myapp:v1
# Keyless signing (ใช้ OIDC — ไม่ต้องจัดการ Key)
cosign sign myregistry.com/myapp:v1 # Login ผ่าน Browser
cosign verify [email protected] --certificate-oidc-issuer=https://accounts.google.com myregistry.com/myapp:v1
Network Security — ความปลอดภัยของ Docker Network
โดยค่าเริ่มต้น Container ทั้งหมดในเครือข่ายเดียวกันจะ คุยกันได้หมด ต้องแยก Network และจำกัดการเข้าถึง:
# ห้ามใช้ --net=host (Container ใช้ Network ของ Host โดยตรง)
docker run --net=host myapp:v1 # อันตราย!
# ใช้ Custom bridge network แทน
docker network create --driver bridge app-network
docker network create --driver bridge db-network
# App เข้าถึงได้เฉพาะ app-network
docker run --network=app-network myapp:v1
# DB เข้าถึงได้เฉพาะ db-network
docker run --network=db-network postgres:16
# เชื่อม App กับ DB ผ่าน db-network
docker network connect db-network app-container
# Docker Compose: แยก Network
services:
web:
networks: [frontend, backend]
api:
networks: [backend, database]
db:
networks: [database]
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # ไม่มี Internet access
database:
driver: bridge
internal: true # ไม่มี Internet access
Logging Driver — บันทึก Log อย่างปลอดภัย
Log เป็นสิ่งสำคัญสำหรับ Security monitoring และ Forensics ต้องตั้งค่า Logging driver ให้เหมาะสม:
# ตรวจสอบ Logging driver ปัจจุบัน
docker info --format '{{.LoggingDriver}}'
# ตั้งค่า Logging driver ใน daemon.json
# /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "5",
"compress": "true"
}
}
# Docker Compose: ส่ง Log ไป Centralized logging
services:
app:
image: myapp:v1
logging:
driver: "fluentd"
options:
fluentd-address: "localhost:24224"
tag: "app.{{.Name}}"
# ข้อสำคัญ: จำกัดขนาด Log
# ถ้าไม่จำกัด Log อาจเต็ม Disk ทำให้ Host ล่ม (DoS)
Docker Daemon Security
Docker Daemon มีสิทธิ์สูงมาก ต้อง Harden ให้ดี:
# /etc/docker/daemon.json — Hardened configuration
{
"icc": false, # ปิด Inter-container communication
"no-new-privileges": true, # ห้ามเพิ่ม Privileges
"userns-remap": "default", # User namespace remapping
"live-restore": true, # Container อยู่ได้แม้ Daemon restart
"userland-proxy": false, # ใช้ iptables แทน userland proxy
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"default-ulimits": {
"nofile": { "Name": "nofile", "Hard": 64000, "Soft": 64000 },
"nproc": { "Name": "nproc", "Hard": 4096, "Soft": 4096 }
}
}
# Restart Docker daemon
sudo systemctl restart docker
Rootless Docker — รัน Docker โดยไม่ใช้ root
Rootless Docker ทำให้ Docker daemon และ Container ทั้งหมดรันโดย ไม่ต้องใช้ root ลดความเสี่ยงจาก Container escape ได้มาก:
# ติดตั้ง Rootless Docker
dockerd-rootless-setuptool.sh install
# ตรวจสอบว่ารัน Rootless mode
docker info 2>/dev/null | grep -i "root"
# Security Options: rootless
# ข้อจำกัดของ Rootless mode:
# - ไม่สามารถ Bind port ต่ำกว่า 1024 ได้ (ใช้ port > 1024)
# - Overlay2 storage driver ต้องใช้ kernel 5.11+
# - ไม่รองรับ --net=host
# - AppArmor/SELinux อาจต้อง Config เพิ่ม
# สำหรับ Production ที่ต้องการ Security สูง Rootless Docker เป็นทางเลือกที่ดีมาก
Docker + SELinux/AppArmor — เพิ่มชั้นป้องกัน
SELinux และ AppArmor เป็น Linux Security Module ที่เพิ่มชั้นป้องกันให้ Container:
# AppArmor (Ubuntu/Debian)
# ดู AppArmor profile ของ Container
docker inspect --format='{{.AppArmorProfile}}' container_name
# สร้าง Custom AppArmor profile
# /etc/apparmor.d/docker-myapp
profile docker-myapp flags=(attach_disconnected,mediate_deleted) {
# อนุญาตเฉพาะที่จำเป็น
file,
network inet tcp,
network inet udp,
deny /etc/shadow r, # ห้ามอ่าน Shadow file
deny /proc/** w, # ห้ามเขียน /proc
}
# ใช้ Custom profile
docker run --security-opt apparmor=docker-myapp myapp:v1
# SELinux (RHEL/CentOS)
# ตรวจสอบ SELinux status
getenforce # ควรได้ "Enforcing"
# Docker + SELinux
docker run --security-opt label=type:container_t myapp:v1
# ข้อสำคัญ: ห้ามปิด SELinux/AppArmor เพื่อ "แก้ปัญหา"
# ให้แก้ที่ Policy แทน
Docker Security Checklist สำหรับ Production
| หมวด | Check Item | สถานะ |
|---|---|---|
| Image | ใช้ Minimal base image (Alpine, Distroless, Scratch) | [ ] |
| Image | Pin image version + SHA256 digest | [ ] |
| Image | Multi-stage build (แยก Build/Runtime) | [ ] |
| Image | สแกน Vulnerability ทุกครั้ง (Trivy/Grype) | [ ] |
| Image | Sign image (DCT/Cosign) | [ ] |
| Runtime | รัน Container ด้วย Non-root user | [ ] |
| Runtime | Read-only filesystem + tmpfs สำหรับ writable paths | [ ] |
| Runtime | Drop ALL Linux capabilities | [ ] |
| Runtime | จำกัด Memory/CPU/PIDs | [ ] |
| Runtime | ไม่ Mount Docker socket | [ ] |
| Network | ใช้ Custom bridge (ไม่ใช่ default bridge) | [ ] |
| Network | แยก Network ตาม Function (frontend/backend/db) | [ ] |
| Network | ไม่ใช้ –net=host | [ ] |
| Secrets | ไม่ใส่ Secret ใน ENV ของ Dockerfile | [ ] |
| Secrets | ใช้ Docker Secrets หรือ External vault | [ ] |
| Daemon | ปิด ICC (Inter-container communication) | [ ] |
| Daemon | เปิด No-new-privileges | [ ] |
| Logging | จำกัดขนาด Log (max-size, max-file) | [ ] |
| Host | พิจารณาใช้ Rootless Docker | [ ] |
| Host | เปิด SELinux/AppArmor | [ ] |
สรุป — Docker Security Best Practices 2026
Docker Security ไม่ใช่สิ่งที่ทำครั้งเดียวแล้วจบ แต่เป็น กระบวนการต่อเนื่อง ที่ต้องทำตั้งแต่เขียน Dockerfile จนถึง Production เริ่มจาก Minimal base image, รัน Non-root, Read-only filesystem, จำกัด Resources, ใช้ Docker Secrets, สแกน Image ทุกครั้ง, Sign image, แยก Network, Harden Docker daemon และพิจารณาใช้ Rootless Docker
สิ่งที่สำคัญที่สุดคือ Defense in Depth ไม่ใช่พึ่งพาชั้นป้องกันเดียว แต่ซ้อนหลายชั้น ถ้าชั้นหนึ่งถูกเจาะ ชั้นถัดไปยังป้องกันอยู่ รัน Docker Bench for Security เป็นประจำ และแก้ไขทุก Warning ที่พบ จะช่วยให้ Docker Infrastructure ของคุณปลอดภัยในระดับที่ยอมรับได้