Docker คืออะไร?
วัดบน Docker Desktop 4.81.0, Engine 29.6.1 (API 1.55), macOS บน Apple Silicon, 2026-09-30
องค์ประกอบทั้งสี่ของ Docker
หัวข้อที่มีชื่อว่า “องค์ประกอบทั้งสี่ของ Docker”Docker ไม่ใช่โปรแกรมเดียว แต่เป็น ecosystem ของ component ที่ทำงานร่วมกัน การเข้าใจแต่ละส่วนช่วยให้คุณคิดตามได้ง่ายขึ้นว่าเกิดอะไรขึ้นเมื่อรันคำสั่ง docker
Docker client (CLI docker)
หัวข้อที่มีชื่อว่า “Docker client (CLI 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 lsNAME DESCRIPTION DOCKER ENDPOINTdefault Current DOCKER_HOST based configuration unix:///var/run/docker.sockdesktop-linux * Docker Desktop unix:///Users/avetavos/.docker/run/docker.sockorbstack OrbStack unix:///Users/avetavos/.orbstack/run/docker.sockpodman 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
Docker daemon (dockerd)
หัวข้อที่มีชื่อว่า “Docker daemon (dockerd)”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
$ docker version --format '{{json .Server.Components}}' # names and versions onlyEngine 29.6.1containerd v2.2.5runc 1.3.6docker-init 0.19.0docker-init คือ init process ตัวเล็กที่คุณได้เมื่อใช้ docker run --init (บทเรียน first-container แสดงว่าทำไมมันสำคัญ)
พิสูจน์: คุยกับ daemon โดยไม่ใช้ CLI
หัวข้อที่มีชื่อว่า “พิสูจน์: คุยกับ daemon โดยไม่ใช้ CLI”เพราะ CLI เป็นแค่ REST client curl จึงทำงานเดียวกันได้ /version คือ API ที่อยู่เบื้องหลัง docker version:
#!/bin/sh# Docker Desktop for Mac socket path; on Linux use /var/run/docker.sockcurl -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 arm64API เวอร์ชันขั้นต่ำ 1.40 คือวิธีที่ engine ยังรองรับ client รุ่นเก่า ทีนี้ลองสร้าง start และอ่านผลของ container ด้วย HTTP ล้วน ๆ:
#!/bin/shS="$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/startcurl -s --unix-socket "$S" -X POST http://localhost/containers/docker-demo-api/waitdocker logs docker-demo-apidocker rm docker-demo-apioutput จริง:
{"Id":"31bdcccd9aea8df38688f779d2f4c40d3bf0aac7fb14fecc6941a61ac4db7f60","Warnings":[]} # create -> 201 # start -> 204 No Content{"StatusCode":0} # wait -> 200hello from the APIdocker-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
หัวข้อที่มีชื่อว่า “Registry”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"]
docker buildx imagetools inspect พิมพ์ชื่อเต็มที่ daemon resolve ได้ พร้อม digest และ manifest หนึ่งชุดต่อ platform:
$ docker buildx imagetools inspect nginx:1.31-alpineName: docker.io/library/nginx:1.31-alpineMediaType: application/vnd.oci.image.index.v1+jsonDigest: 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 nginx - CLI ส่ง request ไปที่
dockerdผ่าน Unix socket: ถ้าไม่มี image ก็ pull (POST /images/create) จากนั้นPOST /containers/createและPOST /containers/{id}/startและ attach เพื่อ stream output dockerdตรวจ cache ในเครื่องหาnginx:latest- ถ้าไม่มี image
dockerdติดต่อ Docker Hub ยืนยันตัวตนถ้าจำเป็น และดึง layer ของ image - layer ถูกเก็บใน cache ในเครื่อง (
Docker Root Dir: /var/lib/dockerในdocker info— อยู่ใน Desktop VM เมื่อใช้ Mac) dockerdสั่ง containerd/runc ให้สร้าง container จาก image และ start process- 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 createcontainer attachcontainer startcontainer diecontainer destroydestroy ปรากฏเพราะ container ถูก start ด้วย --rm ถ้าไม่มี ลำดับจะหยุดที่ die และ container ยังคงอยู่ใน docker ps -a
Reading AI-generated code
หัวข้อที่มีชื่อว่า “Reading AI-generated code”Compose file “production” ที่ AI เขียนสำหรับเว็บ static:
🐛 หา bug ในโค้ดนี้
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 d2482869a6b8tag เดียวกันตอนนี้ชี้ไปที่ 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 ด้วย):
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)
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”docker คือรีโมตคอนโทรล dockerd คือออฟฟิศที่เป็นเจ้าของทุก image, container, network และ volume มันส่งงานระดับล่างให้ containerd และ runc ซึ่งขอ namespace กับ cgroup จาก kernel แล้ว start process ของคุณ image คือ template แบบอ่านอย่างเดียวที่อ้างถึงด้วยชื่อ tag และ — ส่วนเดียวที่ไม่เปลี่ยน — digest container คือ template นั้นบวก writable layer บวก process ที่กำลังรัน
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”| ทางเลือก | ข้อดี | ข้อเสีย |
|---|---|---|
| Container | เบา แชร์ host kernel start เร็ว เหมาะกับรันหลาย instance | isolation อ่อนกว่า VM เพราะแชร์ kernel |
| VM | isolation แข็งแรงผ่าน hypervisor เต็มรูปแบบ | หนัก — ต้องมี guest OS ของตัวเองและใช้ทรัพยากรมากกว่ามาก |
| Daemon ที่มี socket API | client ใดก็ขับได้ (CLI, curl, CI, IDE) และใช้ engine ระยะไกลได้ง่าย | สิทธิ์เข้า socket เท่ากับ root บน engine host |
Tag (nginx:1.31-alpine) เทียบกับ digest (@sha256:…) | tag อ่านง่ายและได้ patch update | tag ย้ายได้ มีแต่ digest ที่ pin ไบต์จริง |
Common mistakes
หัวข้อที่มีชื่อว่า “Common mistakes”- ปนกันระหว่าง image กับ container — image คือ template ที่นิ่ง container คือ instance ที่กำลังรัน
- ใช้ tag
latestบน production ทำให้ build ไม่ reproducible เพราะlatestชี้ไปเวอร์ชันอื่นได้เมื่อเวลาผ่านไป - คิดว่า container ปลอดภัยเท่า VM โดยไม่เสริมความแข็งแรง เช่น ปล่อยให้ process ใน container รันเป็น root
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “คำสั่ง
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ให้