jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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.

12 MIN READ Updated JUL 11, 2026

Đâ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ápbộ 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 5Phầ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ếplỗ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.

Observe logs · ps · inspect Isolate exec · events Reproduce run --rm Fix rebuild · config verify → repeat until green
Don't change flags at random — walk the loop: observe what Docker reports, isolate one variable, reproduce minimally, fix one thing, then verify and repeat
  1. Quan sát — Docker báo gì? docker ps -a, docker logs, exit code, docker inspect State.
  2. Cô lập — Một container, một mạng, một thay đổi mỗi lần.
  3. Tái hiệndocker run hoặc compose.yaml nhỏ nhất vẫn fail.
  4. 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:

CommandCho 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> shShell 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 eventsLuồng thời gian thực: create, start, die, OOM
docker statsCPU/RAM/mạng trực tiếp theo container
docker ps -aMọ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 logsdocker 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/ENTRYPOINT hoặc thiếu file trong image.

Trên Kubernetes bạn gặp CrashLoopBackOffImagePullBackOff — 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:80 fail với bind: address already in use hoặc port 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 ps trống; docker ps -a hiện Exited vài giây sau start.
  • NGUYÊN NHÂN: Không có tiến trình foreground chạy lâu — CMD một lần, server thoát, hoặc sai CMD trong Dockerfile.
  • SỬA: Đảm bảo PID 1 sống; xem docker inspect Cmd/Entrypoint.

Bị giết vì hết RAM

  • TRIỆU CHỨNG: STATUS OOMKilled hoặc exit 137.
  • NGUYÊN NHÂN: Vượt giới hạn cgroup bộ nhớ (--memory hoặ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; ls trong 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 2Phầ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:5432 hoặc ENOTFOUND db.
  • NGUYÊN NHÂN: localhost trong 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; log Permission denied.
  • NGUYÊN NHÂN: Lệch UID/GID giữa user container và file bind mount trên host.
  • SỬA: chown trên host khớp USER, --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 df rồ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
CommandXóa gìRủi ro mất dữ liệu
docker system pruneStopped containers + writable layer, unused networks, dangling images, build cacheTrung bình — dữ liệu ghi trong stopped container mất; volume giữ
docker system prune -aNhư trên + mọi image không container nào dùngTrung bình/cao — phải pull/build lại
docker system prune -a --volumesNhư trên + unused anonymous volumesCao — anonymous volume vẫn có thể chứa data
docker volume prune --allMọi volume không dùng, gồm cả named volumeRất cao — backup và target rõ trước khi chạy
docker builder pruneBuild cache onlyThấ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 --volumes nhắm anonymous volume, còn docker volume prune --all mới gồm unused named volume. Backup, inspect và ưu tiên docker 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-crash

2. 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-b

3. 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 lab08net

4. 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-capped

5. 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 -f

6. 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_healthy
docker 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 down

Nâng cao: Mô phỏng OOMKilled với --memory thấp và process ngốn RAM; xác nhận exit 137OOMKilled: 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 -a cho biết đã xảy ra gì; docker exec cho 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 configtrạng thái health giải thích lệch Compose “máy tôi chạy được”.
  • docker system df trước prune — 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).