Docker for Developers · Part 8 — Debugging & Troubleshooting Docker
Playbook debug Docker theo evidence: status, logs, inspect, events, stats; xử lý exit code, OOM, port, cache, network và disk.
Đây là Phần 8 của chặng nền tảng về Docker → Compose → Kubernetes. Hầu hết lỗi Docker trông như “ma thuật” cho đến khi bạn có phương pháp và bộ công cụ nhỏ. Ở Phần 1 bạn học vòng đời cơ bản; ở Phần 3, Phần 4, Phần 5 và Phần 6 bạn nối volume, mạng và stack Compose. Phần này là nền tảng: khi có gì hỏng lúc 2 giờ sáng, bạn biết chạy lệnh gì tiếp và lỗi thực sự nghĩa là gì.
Mỗi phần kết thúc bằng bài tập; coi chúng như diễn tập sự cố, không phải câu đố.
Debug giỏi không phải nhớ nhiều lệnh hơn người khác. Debug giỏi là giữ được thứ tự quan sát: trạng thái, log, event, config, network, resource. Bạn càng căng, càng phải đi theo checklist; đừng sửa ba biến cùng lúc rồi tự tạo thêm một sự cố mới.
Phương pháp debug tổng quát
Khi container hành xử lạ, đừng đổi flag bừa bãi.
- Quan sát — Docker báo gì?
docker ps -a,docker logs, exit code,docker inspectState. - Cô lập — Một container, một mạng, một thay đổi mỗi lần.
- Tái hiện —
docker runhoặccompose.yamlnhỏ nhất vẫn fail. - Sửa — Một fix, chạy lại cùng lệnh, xác nhận STATUS và logs.
Quy tắc vàng: tiến trình chính là PID 1. Nó thoát thì container thoát — luôn hỏi “PID 1 là gì và vì sao nó chết?”.
Bộ công cụ debug
Các lệnh này trả lời câu hỏi khác nhau:
| Command | Cho biết gì |
|---|---|
docker logs <c> | stdout/stderr của app (crash, stack trace) |
docker logs -f <c> | Theo log trực tiếp |
docker logs --tail 50 <c> | Chỉ N dòng cuối |
docker logs --since 10m <c> | Log 10 phút gần nhất |
docker exec -it <c> sh | Shell trong container đang chạy — file, env, DNS từ góc nhìn nó |
docker inspect <c> | JSON đầy đủ: State, Cmd, mount, mạng, exit code |
docker inspect --format='{{.State.ExitCode}}' <c> | Lấy một field không cần lướt JSON |
docker events | Luồng thời gian thực: create, start, die, OOM |
docker stats | CPU/RAM/mạng trực tiếp theo container |
docker ps -a | Mọi container + STATUS + gợi ý exit |
docker diff <c> | File đổi ở layer ghi-được so với image |
docker top <c> | Tiến trình trong container (từ host) |
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
docker logs --tail 100 -f api
docker inspect api --format 'Exit={{.State.ExitCode}} OOM={{.State.OOMKilled}} Error={{.State.Error}}'
docker events --filter container=api --since 30m
docker stats --no-stream
Nếu container đã dừng, exec fail — dùng docker logs và docker inspect trước, hoặc override entrypoint (bên dưới).
Đọc exit code
docker ps -a hiện Exited (N) — N là exit code của PID 1.
EXIT CODE QUICK REFERENCE (Linux containers)
┌──────┬────────────────────────────────────────────────────────────┐
│ Code │ Meaning │
├──────┼────────────────────────────────────────────────────────────┤
│ 0 │ Success — process finished normally (or idle server died) │
│ 1 │ General application error (check logs) │
│ 125 │ `docker run` itself failed (daemon, bad flag) │
│ 126 │ Command found but not executable (permissions) │
│ 127 │ Command not found (wrong CMD, missing binary in PATH) │
│ 137 │ SIGKILL — often OOMKilled (128 + 9) or `docker kill` │
│ 143 │ SIGTERM — graceful stop (`docker stop`, orchestrator) │
└──────┴────────────────────────────────────────────────────────────┘
docker inspect broken --format '{{.State.ExitCode}} {{.State.OOMKilled}} {{.State.FinishedAt}}'
- 137 +
OOMKilled: true→ kernel giết vì bộ nhớ — tăng limit hoặc sửa leak. - 143 sau
docker stop→ bình thường; không phải bug. - 127 ngay khi start → sai
CMD/ENTRYPOINThoặc thiếu file trong image.
Trên Kubernetes bạn gặp CrashLoopBackOff và ImagePullBackOff — cùng gốc với “container thoát ngay” và “pull image fail” bên dưới; Phần 9 đặt tên theo ngữ cảnh cluster.
Cẩm nang lỗi thường gặp
Mỗi lỗi: TRIỆU CHỨNG → NGUYÊN NHÂN → CÁCH SỬA.
Cổng đã dùng
- TRIỆU CHỨNG:
docker run -p 8080:80fail vớibind: address already in usehoặcport is already allocated. - NGUYÊN NHÂN: Tiến trình khác (hoặc container khác) đang listen cổng host đó.
- FIX:
# who owns the port? (macOS/Linux examples)
lsof -i :8080
docker ps --format '{{.Names}} {{.Ports}}' | grep 8080
# pick another host port, or stop the conflicting container
docker stop other && docker rm other
docker run -p 8081:80 nginx
Container thoát ngay
- TRIỆU CHỨNG:
docker pstrống;docker ps -ahiệnExitedvài giây sau start. - NGUYÊN NHÂN: Không có tiến trình foreground chạy lâu —
CMDmột lần, server thoát, hoặc saiCMDtrong Dockerfile. - SỬA: Đảm bảo PID 1 sống; xem
docker inspectCmd/Entrypoint.
Bị giết vì hết RAM
- TRIỆU CHỨNG: STATUS
OOMKilledhoặc exit 137. - NGUYÊN NHÂN: Vượt giới hạn cgroup bộ nhớ (
--memoryhoặc áp lực host). - FIX:
docker stats --no-stream # who is using RAM?
docker run --memory 512m --memory-swap 512m myapp # cap (swap=mem disables extra swap)
# in compose.yaml: deploy.resources.limits.memory (Compose v3+) or mem_limit on service
Sửa leak hoặc tăng limit; trên Docker Desktop tăng Resources → Memory.
Sai kiến trúc CPU
- TRIỆU CHỨNG: Thoát ngay; log
exec format error. - NGUYÊN NHÂN: Binary arm64 trên host amd64 (hoặc ngược lại).
- FIX:
docker run --platform linux/amd64 myimage:tag
# or build for the target platform
docker build --platform linux/amd64 -t myapp .
Không tìm thấy file entrypoint
- TRIỆU CHỨNG: Báo không có file dù file “có”.
- NGUYÊN NHÂN: Thường là CRLF trong script, sai path, hoặc không COPY binary vào image.
- SỬA:
dos2unix; kiểm tra COPY;lstrong image.
Pull image thất bại
- TRIỆU CHỨNG:
manifest unknown;pull access denied; rate limit. - NGUYÊN NHÂN: Sai tag, image private, chưa login, rate limit Hub.
- FIX:
docker login
docker pull nginx:1.28-alpine # pin a supported tag that exists on Docker Hub
docker manifest inspect myorg/myapp:v2 # verify tag exists (BuildKit)
K8s hiện ImagePullBackOff — sửa tên image, credential hoặc registry rồi deploy lại.
Cache build cũ
- TRIỆU CHỨNG: Build vẫn dùng dependency/file cũ.
- NGUYÊN NHÂN: Cache layer tái dùng vì thứ tự Dockerfile/context.
- FIX:
docker build --no-cache -t myapp .
# deliberate cache bust: ARG CACHEBUST=1 before COPY, or touch dependency lockfile layer
Xem Phần 2 và Phần 7 về kỷ luật cache.
Không kết nối DB/container khác
- TRIỆU CHỨNG:
ECONNREFUSED 127.0.0.1:5432hoặcENOTFOUND db. - NGUYÊN NHÂN:
localhosttrong container là chính nó; hoặc default bridge không có DNS theo tên. - SỬA: Dùng tên service trên user-defined network; trong Compose dùng key service làm hostname.
docker network create appnet
docker run -d --name db --network appnet -e POSTGRES_PASSWORD=x postgres:16-alpine
docker run -d --name api --network appnet -e DATABASE_URL=postgres://postgres:secret@db:5432/app myapi
docker exec api getent hosts db # should resolve
Permission denied trên volume mount
- TRIỆU CHỨNG: Không ghi được
/data; logPermission denied. - NGUYÊN NHÂN: Lệch UID/GID giữa user container và file bind mount trên host.
- SỬA:
chowntrên host khớpUSER,--user, hoặc named volume cho prod.
Đầy disk
- TRIỆU CHỨNG: Build/run fail
no space left on device; daemon chậm. - NGUYÊN NHÂN: Layer, image mồ côi, container dừng và build cache chiếm storage.
- SỬA: Xem
docker system dfrồi prune an toàn (phần sau).
Debug container không start được
Khi tiến trình chính chết trước khi exec, override entrypoint và khám phá filesystem:
docker run -it --rm --entrypoint sh myimage:broken
# inside: ls -la, cat /app/start.sh, which node, env
docker inspect myimage:broken --format 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}}'
WON'T START? TRY THIS LADDER
┌──────────────────────────────────────────────────────────────┐
│ 1. docker ps -a + docker logs <c> │
│ 2. docker inspect <c> (ExitCode, OOMKilled, Error) │
│ 3. docker run --entrypoint sh <image> (interactive probe) │
│ 4. docker history <image> (what layers / CMD were baked in) │
│ 5. Rebuild with --no-cache if image content is suspect │
└──────────────────────────────────────────────────────────────┘
Image distroless không có shell — dùng stage debug hoặc docker create + docker cp.
Debug stack Compose
Lỗi đa service cần lệnh theo từng service:
docker compose ps # STATUS per service (incl. health)
docker compose logs -f api # follow one service
docker compose logs --tail=50 db
docker compose config # merged YAML — catch typos & interpolation
docker compose config --quiet # exit 0 only if valid
docker compose exec api sh # shell in running service container
Healthcheck (Phần 6): service phụ thuộc không healthy — kiểm tra lệnh health:
docker compose ps
docker inspect $(docker compose ps -q db) --format '{{json .State.Health}}'
COMPOSE DEBUG FLOW
compose config ──► compose up ──► compose ps
│ │ │
│ └── compose logs -f <svc>
└── fix YAML/env before chasing runtime
depends_on: condition: service_healthy chờ health — health sai thì upstream đứng im.
Dọn dẹp an toàn
Trước prune mạnh, xem sắp xóa gì:
docker system df
docker system df -v # per-image/container breakdown
| Command | Xóa gì | Rủi ro mất dữ liệu |
|---|---|---|
docker system prune | Stopped containers + writable layer, unused networks, dangling images, build cache | Trung bình — dữ liệu ghi trong stopped container mất; volume giữ |
docker system prune -a | Như trên + mọi image không container nào dùng | Trung bình/cao — phải pull/build lại |
docker system prune -a --volumes | Như trên + unused anonymous volumes | Cao — anonymous volume vẫn có thể chứa data |
docker volume prune --all | Mọi volume không dùng, gồm cả named volume | Rất cao — backup và target rõ trước khi chạy |
docker builder prune | Build cache only | Thấp runtime; build lâu hơn |
Cảnh báo: “unused” chỉ nghĩa là hiện không container nào tham chiếu; nó không nghĩa dữ liệu vô giá trị.
docker system prune --volumesnhắm anonymous volume, còndocker volume prune --allmới gồm unused named volume. Backup,inspectvà ưu tiêndocker volume rm <tên>khi biết chính xác target.
docker container prune # stopped containers only
docker image prune -a # unused images
docker volume prune # unused anonymous volumes
docker volume prune --all # include unused named volumes — very dangerous
Bảng tra nhanh
# status & exit
docker ps -a
docker inspect <c> --format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}}'
docker events --since 1h
# logs & live
docker logs --tail 100 <c>
docker logs -f --since 5m <c>
# inside & probe
docker exec -it <c> sh
docker run -it --rm --entrypoint sh <image>
docker top <c> docker diff <c> docker stats
# compose
docker compose ps
docker compose logs -f <svc>
docker compose config
docker compose exec <svc> sh
# disk
docker system df
docker system prune # careful: add -a --volumes only when you mean it
Bài tập / Exercises
Làm như sự cố nhỏ — phá, chẩn đoán, sửa.
1. Container lab08-crash thoát ngay. Tìm nguyên nhân bằng ps, logs, inspect, rồi sửa lệnh run để nó sống.
Lời giải
# broken: one-shot CMD — exits right away
docker run --name lab08-crash alpine echo hello
docker ps -a --filter name=lab08-crash
docker logs lab08-crash
docker inspect lab08-crash --format 'Exit={{.State.ExitCode}} Cmd={{json .Config.Cmd}}'
docker rm lab08-crash
# fix: long-running foreground process (debug) or a real server image
docker run -d --name lab08-crash alpine sleep infinity
docker ps --filter name=lab08-crash # STATUS: Up
docker stop lab08-crash && docker rm lab08-crash2. Gặp port is already allocated trên 8080. Tìm ai chiếm cổng và chạy nginx trên cổng trống.
Lời giải
docker run -d --name lab08-a -p 8080:80 nginx
docker run -d --name lab08-b -p 8080:80 nginx # fails: port allocated
lsof -i :8080 || docker ps --format '{{.Names}} {{.Ports}}' | grep 8080
docker rm lab08-b # failed start may leave it Created
docker run -d --name lab08-b -p 8081:80 nginx # fix: different host port
docker stop lab08-a lab08-b && docker rm lab08-a lab08-b3. Container app không tới Postgres qua localhost:5432. Chẩn đoán và sửa bằng mạng tự tạo và hostname db.
Lời giải
docker network create lab08net
docker run -d --name db --network lab08net \
-e POSTGRES_PASSWORD=secret postgres:16-alpine
# broken mental model: localhost inside api points to api itself
docker run --rm --network lab08net busybox:1.37 \
nc -z -v -w 2 127.0.0.1 5432 || true
# fix: use service name on shared network
docker run --rm --network lab08net busybox:1.37 nslookup db
docker run --rm --network lab08net busybox:1.37 nc -z -v -w 2 db 5432
docker stop db && docker rm db
docker network rm lab08net4. Tìm container ngốn RAM nhất bằng docker stats, rồi giới hạn container bằng --memory.
Lời giải
docker run -d --name lab08-mem1 nginx
docker run -d --name lab08-mem2 -e POSTGRES_PASSWORD=x postgres:16-alpine
docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}"
# cap example (512 MB hard limit)
docker run -d --name lab08-capped --memory 512m --memory-swap 512m nginx
docker inspect lab08-capped --format '{{.HostConfig.Memory}}'
docker stop lab08-mem1 lab08-mem2 lab08-capped
docker rm lab08-mem1 lab08-mem2 lab08-capped5. docker system df báo reclaimable cao. Giải phóng disk an toàn không dùng --volumes trước; ghi nhận thay đổi.
Lời giải
docker system df
docker run --rm hello-world
docker system df # note Images/Containers reclaimable
docker system prune -f
docker system df # reclaimable should drop; named volumes untouched
# optional: build cache only
docker builder prune -f6. Service api unhealthy, web không start. Dùng compose ps, compose logs, health JSON tìm healthcheck fail.
Lời giải
Create compose-lab08.yaml:
services:
api:
image: nginx:alpine
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:9999/"]
interval: 5s
timeout: 3s
retries: 2
web:
image: nginx:alpine
depends_on:
api:
condition: service_healthydocker compose -f compose-lab08.yaml up -d
docker compose -f compose-lab08.yaml ps
docker compose -f compose-lab08.yaml logs api
docker inspect $(docker compose -f compose-lab08.yaml ps -q api) \
--format '{{json .State.Health}}'
# fix: correct health URL (nginx listens on 80)
# edit test to http://localhost/ or disable bad healthcheck, then:
docker compose -f compose-lab08.yaml downNâng cao: Mô phỏng OOMKilled với --memory thấp và process ngốn RAM; xác nhận exit 137 và OOMKilled: true.
Lời giải
docker run -d --name lab08-oom --memory 64m --memory-swap 64m progrium/stress \
--vm 1 --vm-bytes 128M --vm-keep
sleep 3
docker ps -a --filter name=lab08-oom
docker inspect lab08-oom --format 'Exit={{.State.ExitCode}} OOM={{.State.OOMKilled}}'
docker rm lab08-oomĐiểm chính
- Dùng quan sát → cô lập → tái hiện → sửa — đừng đổi năm flag cùng lúc.
docker logs,docker inspect,docker ps -acho biết đã xảy ra gì;docker execcho biết container đang thấy gì.- Exit code (đặc biệt 137 OOM, 127 thiếu lệnh, 143 SIGTERM) thu hẹp tìm kiếm nhanh.
localhost≠ container khác — dùng tên service trên mạng tự tạo (Phần 4).docker compose configvà trạng thái health giải thích lệch Compose “máy tôi chạy được”.docker system dftrướcprune— biết bạn xóa gì, nhất là với--volumes.
Tiếp theo
Phần 9 — Kubernetes Fundamentals — Pod, Deployment, Service và cùng tư duy debug qua kubectl logs, describe và Events (CrashLoopBackOff, ImagePullBackOff).