ข้ามไปยังเนื้อหา

Docker คืออะไร?

วัดบน Docker Desktop 4.81.0, Engine 29.6.1 (API 1.55), macOS บน Apple Silicon, 2026-09-30

Docker ไม่ใช่โปรแกรมเดียว แต่เป็น ecosystem ของ component ที่ทำงานร่วมกัน การเข้าใจแต่ละส่วนช่วยให้คุณคิดตามได้ง่ายขึ้นว่าเกิดอะไรขึ้นเมื่อรันคำสั่ง docker

CLI docker คือโปรแกรมที่คุณใช้งานโดยตรง เมื่อคุณพิมพ์คำสั่งอย่าง docker run, docker build หรือ docker pull CLI จะแปลคำสั่งนั้นเป็น REST API request แล้วส่งให้ Docker daemon ตัว client เองไม่ได้ทำงานหนัก — มันเป็นแค่ API client บาง ๆ

โดยค่าเริ่มต้น client คุยกับ daemon ผ่าน Unix socket ที่ /var/run/docker.sock สำหรับ daemon ระยะไกล (เช่น Docker host บน cloud VM) client เชื่อมต่อผ่าน SSH หรือผ่าน TCP ร่วมกับ TLS ได้

CLI จะคุยกับ daemon ตัวไหนถูกกำหนดโดย context บน Mac เครื่องนี้มี engine อื่นติดตั้งอยู่ด้วย docker context ls จึงมีสี่รายการ เครื่องหมาย * คือตัวที่ใช้งานอยู่:

$ docker context ls
NAME DESCRIPTION DOCKER ENDPOINT
default Current DOCKER_HOST based configuration unix:///var/run/docker.sock
desktop-linux * Docker Desktop unix:///Users/avetavos/.docker/run/docker.sock
orbstack OrbStack unix:///Users/avetavos/.orbstack/run/docker.sock
podman Podman unix:///var/folders/.../podman-machine-default-api.sock

บน Docker Desktop socket คือ ~/.docker/run/docker.sock (และ /var/run/docker.sock เป็น symlink ไปยังมันบน Mac เครื่องนี้) บน Linux server เป็น /var/run/docker.sock ธรรมดาที่เป็นของกลุ่ม docker

daemon คือ process เบื้องหลังที่ทำงานตลอดและทำงานจริง มันฟังที่ Unix socket (หรือ TCP port) และจัดการทุก API call ที่ client ส่งมา daemon รับผิดชอบ:

  • ดึงและ cache image จาก registry
  • สร้างและรัน container จาก image เหล่านั้น
  • จัดการ network เพื่อให้ container คุยกันเองและคุยกับโลกภายนอกได้
  • จัดการ volume สำหรับข้อมูลถาวรที่อยู่ได้นานกว่าอายุของ container

ตัว dockerd เองไม่ได้รัน process ของคุณ มันมอบหมายให้ containerd (วงจรชีวิตของ image และ container) ซึ่งมอบหมายต่อให้ runc (เครื่องมือระดับล่างที่ตั้งค่า namespace กับ cgroup แล้ว exec process ของคุณ) เวอร์ชันของทั้งสองแสดงใน docker version:

flowchart TB
  cli["docker CLI"]
  dockerd["dockerd (Engine 29.6.1)"]
  containerd["containerd v2.2.5"]
  runc["runc 1.3.6"]
  proc["your process"]
  cli -->|REST over socket| dockerd --> containerd --> runc --> proc
จาก CLI ลงไปถึง process ของคุณ
$ docker version --format '{{json .Server.Components}}' # names and versions only
Engine 29.6.1
containerd v2.2.5
runc 1.3.6
docker-init 0.19.0

docker-init คือ init process ตัวเล็กที่คุณได้เมื่อใช้ docker run --init (บทเรียน first-container แสดงว่าทำไมมันสำคัญ)

เพราะ CLI เป็นแค่ REST client curl จึงทำงานเดียวกันได้ /version คือ API ที่อยู่เบื้องหลัง docker version:

scripts/api-version.sh
#!/bin/sh
# Docker Desktop for Mac socket path; on Linux use /var/run/docker.sock
curl -s --unix-socket "$HOME/.docker/run/docker.sock" http://localhost/version

ตัดเหลือเฉพาะ field ที่สนใจ (Version, ApiVersion, MinAPIVersion, KernelVersion, Os, Arch):

Version 29.6.1 | ApiVersion 1.55 | MinAPIVersion 1.40 | KernelVersion 6.12.76-linuxkit | Os linux | Arch arm64

API เวอร์ชันขั้นต่ำ 1.40 คือวิธีที่ engine ยังรองรับ client รุ่นเก่า ทีนี้ลองสร้าง start และอ่านผลของ container ด้วย HTTP ล้วน ๆ:

scripts/api-run.sh
#!/bin/sh
S="$HOME/.docker/run/docker.sock"
curl -s --unix-socket "$S" -H 'Content-Type: application/json' -X POST \
-d '{"Image":"alpine:3.24","Cmd":["echo","hello from the API"]}' \
'http://localhost/containers/create?name=docker-demo-api'
curl -s --unix-socket "$S" -X POST http://localhost/containers/docker-demo-api/start
curl -s --unix-socket "$S" -X POST http://localhost/containers/docker-demo-api/wait
docker logs docker-demo-api
docker rm docker-demo-api

output จริง:

{"Id":"31bdcccd9aea8df38688f779d2f4c40d3bf0aac7fb14fecc6941a61ac4db7f60","Warnings":[]} # create -> 201
# start -> 204 No Content
{"StatusCode":0} # wait -> 200
hello from the API
docker-demo-api

นี่คือสิ่งที่ docker run ทำทุกประการ บวกกับ attach เพื่อให้ output ไหลมาที่ terminal ของคุณ เอกสารอ้างอิงของทุก endpoint อยู่ที่ Engine API (เอกสารระบุเวอร์ชัน API ที่คุณใช้อยู่)

image คือ แพ็กเกจแบบ layer ที่อ่านได้อย่างเดียว ซึ่งบรรจุทุกอย่างที่ต้องใช้เพื่อรัน process: โค้ดแอป, filesystem ของ OS พื้นฐาน, runtime library, environment variable และคำสั่งเริ่มต้นที่จะรัน image ถูก build จาก Dockerfile และระบุด้วยชื่อกับ tag เช่น nginx:1.31-alpine

คำสั่ง RUN, COPY และ ADD แต่ละคำสั่งใน Dockerfile สร้าง filesystem layer หนึ่งชั้น (คำสั่งที่เป็น metadata อย่าง ENV หรือ CMD ไม่เพิ่ม layer — โมดูล images พิสูจน์ไว้) layer ถูก cache และแชร์ข้าม image ทำให้ใช้ดิสก์น้อยและ rebuild เร็ว

registry คือ server ที่เก็บและแจกจ่าย image registry สาธารณะเริ่มต้นคือ Docker Hub (docker.io) เมื่อคุณรัน docker pull nginx daemon จะติดต่อ docker.io หา repository nginx และดาวน์โหลด image ลง cache ในเครื่อง

registry จัดระเบียบ image เป็น repository โดย repository หนึ่งเก็บ image เดียวกันได้หลายเวอร์ชัน แยกกันด้วย tag:

flowchart TB
  ref["docker.io/library/nginx:1.31-alpine"]
  ref --> host["registry host: docker.io"]
  ref --> ns["namespace: library (official images)"]
  ref --> repo["repository name: nginx"]
  ref --> tag["tag: 1.31-alpine"]
โครงสร้างของ image reference

docker buildx imagetools inspect พิมพ์ชื่อเต็มที่ daemon resolve ได้ พร้อม digest และ manifest หนึ่งชุดต่อ platform:

$ docker buildx imagetools inspect nginx:1.31-alpine
Name: docker.io/library/nginx:1.31-alpine
MediaType: application/vnd.oci.image.index.v1+json
Digest: sha256:df221db836e1754089190208cee7eeda94f233197056426eda74a43ab1abeac2
Manifests:
Name: docker.io/library/nginx:1.31-alpine@sha256:0530961f...
Platform: linux/amd64
...

prefix docker.io/library/ ที่ CLI ซ่อนไว้เมื่อคุณพิมพ์แค่ nginx คือ registry host และ namespace library สำหรับ official image index แสดง manifest หนึ่งชุดต่อ platform ซึ่งเป็นเหตุผลที่ tag เดียวใช้ได้ทั้ง amd64 และ arm64 สำเนาในเครื่อง Mac นี้เป็น arm64

registry ส่วนตัว (Amazon ECR, GitHub Container Registry, registry ที่ host เอง) ทำงานแบบเดียวกัน — เพียงแค่ docker login เพื่อยืนยันตัวตนก่อน push หรือ pull

ลำดับที่ทุกคำสั่ง docker run ทำตาม:

flowchart TB
  you["You (terminal): docker run nginx"]
  cli["docker CLI"]
  daemon["dockerd (daemon)"]
  registry["Docker Hub (or other registry)"]
  cache["Local image cache"]
  container["Running container (isolated process on host kernel)"]
  you --> cli
  cli -->|REST API over Unix socket| daemon
  daemon -->|Image not in local cache| registry
  registry -->|Pull image layers| cache
  cache -->|Create container from image| container
เกิดอะไรขึ้นเมื่อคุณรัน docker run
  1. คุณพิมพ์ docker run nginx
  2. CLI ส่ง request ไปที่ dockerd ผ่าน Unix socket: ถ้าไม่มี image ก็ pull (POST /images/create) จากนั้น POST /containers/create และ POST /containers/{id}/start และ attach เพื่อ stream output
  3. dockerd ตรวจ cache ในเครื่องหา nginx:latest
  4. ถ้าไม่มี image dockerd ติดต่อ Docker Hub ยืนยันตัวตนถ้าจำเป็น และดึง layer ของ image
  5. layer ถูกเก็บใน cache ในเครื่อง (Docker Root Dir: /var/lib/docker ใน docker info — อยู่ใน Desktop VM เมื่อใช้ Mac)
  6. dockerd สั่ง containerd/runc ให้สร้าง container จาก image และ start process
  7. standard output ของ container ถูก stream กลับผ่าน dockerd ไปยัง terminal ของคุณผ่าน CLI

คุณดูฝั่ง daemon ของขั้นที่ 6-7 ได้ด้วย docker events ขณะรัน container ชั่วคราวใน terminal อื่น:

$ docker events --filter container=docker-demo-ev --format '{{.Type}} {{.Action}}'
container create
container attach
container start
container die
container destroy

destroy ปรากฏเพราะ container ถูก start ด้วย --rm ถ้าไม่มี ลำดับจะหยุดที่ die และ container ยังคงอยู่ใน docker ps -a

Compose file “production” ที่ AI เขียนสำหรับเว็บ static:

🐛 หา bug ในโค้ดนี้

compose.yaml
services:
web:
image: nginx:latest # "always get the newest, most secure nginx"
ports:
- "80:80"
restart: unless-stopped
เฉลย

latest เป็นแค่ชื่อ tag เป็นตัวชี้ที่ย้ายได้ ไม่ใช่คำสัญญาเรื่องความใหม่หรือความปลอดภัย และไฟล์นี้ไม่มีทางรู้ว่าเดือนหน้าจะรันอะไร พิสูจน์ที่นี่ ก่อนอื่นด้วยการย้าย tag ระหว่าง image ในเครื่องสองตัว:

$ docker tag alpine:3.24 demo-app:latest && docker image ls demo-app --format '{{.Tag}} {{.ID}}'
latest 33bee74c45f3
$ docker tag busybox:latest demo-app:latest && docker image ls demo-app --format '{{.Tag}} {{.ID}}'
latest d2482869a6b8

tag เดียวกันตอนนี้ชี้ไปที่ image คนละตัว บน registry เมื่อ 2026-09-30 docker buildx imagetools inspect แสดงว่า nginx:latest, nginx:1, nginx:1.31 และ nginx:mainline ทั้งหมด resolve เป็น digest sha256:abe47724… ส่วน nginx:stable เป็นอีกตัว (sha256:b972f831…) คุณจะได้สายไหนเปลี่ยนไปวันที่ maintainer ย้าย tag และ docker compose pull จะอัปเกรดให้เงียบ ๆ ให้ pin tag เวอร์ชัน (และเพื่อ reproducibility ก็ pin digest ด้วย):

compose.yaml
services:
web:
image: nginx:1.31-alpine
ports:
- "127.0.0.1:8080:80"
restart: unless-stopped

การ pin แลกการอัปเกรดอัตโนมัติกับ reproducibility: คุณต้องรับหน้าที่ยก tag เอง การแก้ยัง bind published port ไว้ที่ 127.0.0.1 เพราะ "80:80" publish บนทุก interface (ดูโมดูล networking)

docker คือรีโมตคอนโทรล dockerd คือออฟฟิศที่เป็นเจ้าของทุก image, container, network และ volume มันส่งงานระดับล่างให้ containerd และ runc ซึ่งขอ namespace กับ cgroup จาก kernel แล้ว start process ของคุณ image คือ template แบบอ่านอย่างเดียวที่อ้างถึงด้วยชื่อ tag และ — ส่วนเดียวที่ไม่เปลี่ยน — digest container คือ template นั้นบวก writable layer บวก process ที่กำลังรัน

ทางเลือกข้อดีข้อเสีย
Containerเบา แชร์ host kernel start เร็ว เหมาะกับรันหลาย instanceisolation อ่อนกว่า VM เพราะแชร์ kernel
VMisolation แข็งแรงผ่าน hypervisor เต็มรูปแบบหนัก — ต้องมี guest OS ของตัวเองและใช้ทรัพยากรมากกว่ามาก
Daemon ที่มี socket APIclient ใดก็ขับได้ (CLI, curl, CI, IDE) และใช้ engine ระยะไกลได้ง่ายสิทธิ์เข้า socket เท่ากับ root บน engine host
Tag (nginx:1.31-alpine) เทียบกับ digest (@sha256:…)tag อ่านง่ายและได้ patch updatetag ย้ายได้ มีแต่ digest ที่ pin ไบต์จริง
  • ปนกันระหว่าง image กับ container — image คือ template ที่นิ่ง container คือ instance ที่กำลังรัน
  • ใช้ tag latest บน production ทำให้ build ไม่ reproducible เพราะ latest ชี้ไปเวอร์ชันอื่นได้เมื่อเวลาผ่านไป
  • คิดว่า container ปลอดภัยเท่า VM โดยไม่เสริมความแข็งแรง เช่น ปล่อยให้ process ใน container รันเป็น root
  • “คำสั่ง docker รัน container ของฉัน” มันแค่ส่ง HTTP request ส่วน dockerd (ผ่าน containerd และ runc) เป็นผู้รัน ปิด CLI แล้ว container แบบ detached ก็ยังรันต่อ
  • “/var/run/docker.sock บน Mac คือ socket ของ Linux daemon” บน Docker Desktop มันเป็น symlink ไปยัง socket ที่ Desktop proxy เข้า VM filesystem ของ daemon รวมถึง /var/lib/docker อยู่ใน VM
  • “nginx เป็นคนละ image กับ docker.io/library/nginx:latest” มันคือ reference เดียวกัน CLI เติมค่า default ของ registry, namespace และ latest ให้
Docker daemon (dockerd) ทำหน้าที่อะไร?
โดยค่าเริ่มต้น Docker CLI ส่งคำสั่งไปที่ไหน?
Docker image คืออะไร?
ใน image reference docker.io/library/nginx:1.31-alpine ส่วน 1.31-alpine คืออะไร?
component ใดตั้งค่า namespace กับ cgroup และ start process ของคุณจริง ๆ?
Compose file เขียน image: nginx:latest ปัญหาคืออะไร?