Làm việc với Docker đủ lâu, bạn sẽ thấy phần lớn vấn đề gặp đi gặp lại. Cùng một triệu chứng, cùng một nguyên nhân, chỉ khác tên container. Bài này là catalogue 20 vấn đề mình gặp nhiều nhất trong quá trình dev và vận hành, tổ chức theo pattern: triệu chứng → nguyên nhân → lệnh debug → fix. Khi gặp lỗi, Ctrl+F triệu chứng, đọc fix trong 30 giây.
Không có lý thuyết dài dòng – mỗi vấn đề là một mục ngắn gọn, self-contained. Nếu bạn cần hiểu sâu tại sao, mình sẽ link đến bài liên quan trong series.
Index nhanh
| # | Vấn đề | Triệu chứng tìm kiếm |
|---|---|---|
| 1 | CrashLoopBackOff | Container restart liên tục, exit code 1 |
| 2 | Cannot connect to Docker daemon | docker ps báo lỗi socket |
| 3 | Port already in use | port is already allocated |
| 4 | Disk full | no space left on device |
| 5 | DNS resolution fail | Name or service not known trong container |
| 6 | OOM kill | Container bị kill, Killed trong log |
| 7 | Permission denied volume | Permission denied khi mount volume |
| 8 | Build cache miss | docker build không cache, chạy lại từ đầu |
| 9 | Image pull quá chậm | docker pull mất vài phút |
| 10 | Container exit ngay | Container start rồi exit ngay lập tức |
| 11 | Healthcheck failing | Status unhealthy |
| 12 | Network timeout | Không ping được container khác |
| 13 | Compose up treo | docker compose up không thoát |
| 14 | Mất file sau restart | File ghi trong container biến mất |
| 15 | CPU 100% | Container ngốn toàn bộ CPU host |
| 16 | Không xóa được image | image is referenced in one or more containers |
| 17 | ENV không inject | Biến môi trường không có trong container |
| 18 | docker exec fail | exec không vào được container |
| 19 | Bind mount không reflect | File host thay đổi không thấy trong container |
| 20 | Registry auth fail | unauthorized khi push/pull |
Vấn đề 1: CrashLoopBackOff – container restart liên tục
Triệu chứng: docker ps hiển thị status Restarting (1) 5 seconds ago, container liên tục restart. Trên Kubernetes, pod ở trạng thái CrashLoopBackOff.
Nguyên nhân: Process chính (PID 1) exit với code khác 0. Thường gặp nhất là: sai CMD/ENTRYPOINT, file config thiếu, biến môi trường không set, hoặc dependency (database, Redis) chưa sẵn sàng.
Debug:
# Xem log container đã stopped
docker logs --tail 50 <container_name>
# Xem exit code của lần chạy trước
docker inspect <container_name> --format '{{.State.ExitCode}}'
# Xem cả lịch sử restart
docker inspect <container_name> --format '{{.State.RestartCount}}'
Fix: Đọc log để tìm lỗi cụ thể. Nếu là dependency chưa ready, thêm restart: on-failure và retry logic trong app. Nếu là sai CMD, kiểm tra Dockerfile – phân biệt shell form (CMD command arg) và exec form (CMD ["command", "arg"]) vì shell form không forward signal đúng cách (xem Phần 5: Container lifecycle & signal handling).
Vấn đề 2: Cannot connect to the Docker daemon
Triệu chứng: Mọi lệnh docker trả về:
Cannot connect to the Docker daemon at unix:///var/run/docker.sock.
Is the docker daemon running?
Nguyên nhân: Docker daemon không chạy, hoặc user hiện tại không có quyền truy cập socket /var/run/docker.sock (permission denied). Trên Linux, socket mặc định thuộc group docker; user phải nằm trong group này.
Debug:
# Kiểm tra daemon có chạy không
systemctl status docker
# Kiểm tra quyền socket
ls -la /var/run/docker.sock
# Kiểm tra user có trong group docker không
groups $USER | grep docker
Fix:
# Start daemon nếu chưa chạy
sudo systemctl start docker
# Thêm user vào group docker (cần logout/login lại)
sudo usermod -aG docker $USER
newgrp docker
sudo docker mỗi lệnh. Việc thêm user vào group docker tương đương cấp quyền root – user có thể chạy container privileged và escape lên host. Chỉ làm với user developer, không làm trên production CI runner chung.Vấn đề 3: Port already in use
Triệu chứng: docker run -p 3000:3000 báo lỗi:
Error: driver failed programming external connectivity on endpoint:
Bind for 0.0.0.0:3000 failed: port is already allocated
Nguyên nhân: Port trên host đã bị chiếm bởi một process khác – có thể là container cũ chưa stop, hoặc app native đang chạy trực tiếp trên host.
Debug:
# Tìm process đang giữ port
sudo lsof -i :3000
# hoặc:
sudo ss -tlnp | grep 3000
# Kiểm tra container nào đang dùng port
docker ps --filter "publish=3000"
Fix:
# Stop container cũ đang chiếm port
docker stop <container_id>
# Hoặc map sang port khác
docker run -p 3001:3000 ...
# Hoặc chỉ bind trên localhost (không expose ra ngoài)
docker run -p 127.0.0.1:3000:3000 ...
docker compose down thay vì Ctrl+C để đảm bảo container được cleanup hoàn toàn, bao gồm cả port mapping. Ctrl+C chỉ gửi SIGINT, container có thể không stop kịp nếu không handle signal đúng.Vấn đề 4: Disk full – no space left on device
Triệu chứng: docker build hoặc docker pull fail với no space left on device. Container đang chạy bắt đầu báo lỗi ghi file.
Nguyên nhân: Docker chiếm dụng disk qua ba thứ: image (mỗi layer là một file trên disk), container log (JSON log mặc định không rotate), và volume orphan (volume của container đã xóa nhưng chưa được prune).
Debug:
# Tổng quan disk usage của Docker
docker system df
# Chi tiết từng image
docker system df -v
# Tìm log container lớn
sudo find /var/lib/docker/containers -name "*.log" -size +100M -exec ls -lh {} \;
Fix:
# Cleanup toàn diện (xóa stopped container, unused image, unused volume)
docker system prune -a --volumes
# Set log rotation mặc định trong /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
# Sau đó: sudo systemctl restart docker
Xem thêm Phần 7: Logging & debugging container để cấu hình log driver phù hợp cho production.
Vấn đề 5: DNS resolution fail trong container
Triệu chứng: Từ trong container, curl http://api-service:8080 báo Name or service not known, nhưng ping IP thì được. Host resolve bình thường.
Nguyên nhân: Container trên custom network không có DNS server tự động, hoặc /etc/resolv.conf bị ghi đè sai. Docker tự động cấp DNS (127.0.0.11) cho container trên bridge network, nhưng nếu bạn mount /etc/resolv.conf từ host hoặc dùng network khác, DNS có thể bị sai.
Debug:
# Kiểm tra DNS config trong container
docker exec <container> cat /etc/resolv.conf
# Thử resolve từ trong container
docker exec <container> nslookup api-service
# Kiểm tra DNS của Docker network
docker network inspect <network_name> | grep -A5 "Config"
Fix:
# Set DNS server cụ thể khi run
docker run --dns 8.8.8.8 --dns 1.1.1.1 ...
# Trong compose:
services:
app:
dns:
- 8.8.8.8
- 1.1.1.1
# Nếu service-to-service trên cùng custom network, dùng service name làm hostname
# Đảm bảo cả hai container cùng network
docker network create mynet
docker run --network mynet --name api ...
docker run --network mynet --name app ...
# Giờ app có thể ping "api"
Vấn đề 6: OOM kill – container bị kill vì hết memory
Triệu chứng: Container đột ngột exit, docker logs có dòng Killed, docker inspect exit code 137 (128 + 9 = SIGKILL). Trên host, dmesg có dòng:
Memory cgroup out of memory: Killed process <pid>
Nguyên nhân: Container vượt memory limit (nếu có set) hoặc host hết memory. Linux OOM killer chọn process trong container và kill nó. Không có cảnh báo trước, không có graceful shutdown – SIGKILL thẳng tay.
Debug:
# Kiểm tra exit code -- 137 nghĩa là bị SIGKILL
docker inspect <container> --format '{{.State.ExitCode}}'
# Xem memory usage realtime
docker stats --no-stream
# Đọc log kernel
dmesg | grep -i "out of memory"
Fix:
# Set memory limit khi run
docker run --memory="512m" --memory-swap="1g" ...
# Trong compose:
services:
app:
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
--memory-swap nên set bằng hoặc lớn hơn --memory – nếu set --memory-swap=0, swap bị disable hoàn toàn.Vấn đề 7: Permission denied trong volume mount
Triệu chứng: Container ghi file vào thư mục mount volume báo Permission denied, mặc dù thư mục host có quyền 755.
Nguyên nhân: UID mismatch giữa user trong container và owner của thư mục trên host. User trong container (ví dụ node với UID 1000) không có quyền ghi vào thư mục host owned by UID 0 (root).
Debug:
# Kiểm tra user đang chạy trong container
docker exec <container> id
# Kiểm tra quyền thư mục host
ls -la /path/to/host/dir
Fix:
# Cách 1: Chạy container với user trùng UID host
docker run -u $(id -u):$(id -g) -v /host/data:/data ...
# Cách 2: Đổi owner thư mục host
sudo chown -R 1000:1000 /host/data
# Cách 3: Dùng Dockerfile với USER
FROM node:20
RUN useradd -m -u 1000 appuser
USER appuser
# Cách 4: Named volume thay vì bind mount -- Docker tự quản lý permission
docker run -v app_data:/data ...
Xem thêm Phần 6: Volume, bind mount & tmpfs để hiểu sâu về các loại mount và permission model.
Vấn đề 8: Build cache miss liên tục
Triệu chứng: docker build chạy lại từ đầu mỗi lần, không cache layer nào. Build time 5-10 phút mỗi lần sửa code.
Nguyên nhân: Thứ tự lệnh trong Dockerfile sai – lệnh thay đổi thường xuyên (COPY source code) đặt trước lệnh ít thay đổi (RUN npm install). Chỉ cần một file thay đổi, toàn bộ cache từ lệnh đó trở đi bị invalidate.
Debug:
# Build với progress để xem từng layer
docker build --progress=plain --no-cache=false .
# Kiểm tra layer cache
docker history <image>
Fix: Sắp xếp Dockerfile từ ít thay đổi nhất đến nhiều thay đổi nhất:
# 1. Base image (không bao giờ thay đổi)
FROM node:20-alpine
# 2. System deps (hiếm khi thay đổi)
RUN apk add --no-cache curl
# 3. Dependencies (chỉ thay đổi khi package.json đổi)
COPY package.json package-lock.json ./
RUN npm ci
# 4. Source code (thay đổi thường xuyên nhất -- để cuối)
COPY . .
# 5. Build (nếu có)
RUN npm run build
CMD ["node", "dist/server.js"]
.dockerignore để loại bỏ node_modules, .git, *.log khỏi build context. Context nhỏ hơn = gửi lên daemon nhanh hơn = build nhanh hơn, và tránh cache miss do file rác. Xem Phần 4: Dockerfile thực chiến để biết thêm best practice.Vấn đề 9: Image pull quá chậm
Triệu chứng: docker pull mất vài phút cho một image 500MB. CI pipeline timeout vì pull image.
Nguyên nhân: Image quá lớn (dùng ubuntu:latest thay vì Alpine, không multi-stage build), registry ở xa (Docker Hub US mà server ở Singapore), hoặc không có local cache.
Debug:
# Xem kích thước image
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
# Phân tích layer size
docker history <image>
Fix:
# Dùng Alpine-based image thay vì full Ubuntu
FROM node:20-alpine # ~50MB thay vì ~300MB
# Multi-stage build -- chỉ giữ artifact cuối
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN go build -o server .
FROM alpine:3.20
COPY --from=builder /app/server /usr/local/bin/
CMD ["server"]
# Set up registry mirror trong /etc/docker/daemon.json
{
"registry-mirrors": ["https://mirror.example.com"]
}
Vấn đề 10: Container exit ngay sau start
Triệu chứng: docker run chạy xong, docker ps không thấy container. docker ps -a thấy status Exited (0) ngay sau khi start.
Nguyên nhân: Process foreground (PID 1) exit ngay lập tức – thường vì CMD không phải là process chạy foreground. Ví dụ: CMD nginx sai (nginx foreground cần nginx -g "daemon off;"), hoặc script chạy xong rồi exit.
Debug:
# Xem container đã exit
docker ps -a --filter "status=exited" --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"
# Xem log
docker logs <container_name>
Fix: Đảm bảo CMD chạy một process foreground. Ví dụ:
# Sai: process chạy xong thì container exit
CMD ["echo", "hello"]
# Đúng: process chạy foreground, giữ container sống
CMD ["node", "server.js"]
# hoặc:
CMD ["nginx", "-g", "daemon off;"]
Vấn đề 11: Healthcheck failing
Triệu chứng: docker ps hiển thị status unhealthy. Container không tự restart (trừ khi dùng --autoheal label kèm autoheal container).
Nguyên nhân: Health check command trả về exit code khác 0. Ba lý do phổ biến: endpoint health check sai (sai path, sai port), timeout quá ngắn (app khởi động chưa kịp), hoặc --start-period không đủ cho warm-up.
Debug:
# Xem health status chi tiết
docker inspect <container> --format '{{json .State.Health}}' | jq
# Chạy thủ công health check command
docker exec <container> curl -f http://localhost:8080/health
# Xem log health check
docker inspect <container> --format '{{range .State.Health.Log}}{{.Output}}{{end}}'
Fix:
HEALTHCHECK --interval=10s --timeout=5s --retries=3 --start-period=30s \
CMD curl -f http://localhost:8080/health || exit 1
Xem Phần 8: Healthcheck & autoheal pattern để hiểu toàn bộ cơ chế health check và autoheal sidecar pattern.
Vấn đề 12: Network timeout giữa các container
Triệu chứng: Container A không kết nối được container B. curl http://service-b:8080 timeout sau 30 giây, nhưng curl http://<ip-service-b>:8080 thì được.
Nguyên nhân: Hai container không cùng Docker network. Docker Compose tự tạo default network, nhưng nếu bạn chạy docker run riêng lẻ, chúng không thấy nhau. Hoặc DNS cache stale – container B bị recreate với IP mới nhưng A vẫn cache IP cũ.
Debug:
# Kiểm tra container A có network nào
docker inspect <container_a> --format '{{json .NetworkSettings.Networks}}' | jq
# Xem IP và network của container B
docker inspect <container_b> --format '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}'
# Test ping từ A
docker exec <container_a> ping <service_b>
Fix:
# Đảm bảo cả hai cùng một custom network
docker network create shared
docker network connect shared <container_a>
docker network connect shared <container_b>
# Trong compose, dùng service name làm hostname -- nó sẽ resolve ra IP
# Nếu IP thay đổi do container recreate, DNS Docker tự cập nhật
Vấn đề 13: docker compose up treo không thoát
Triệu chứng: docker compose up chạy xong nhưng terminal không trả lại prompt – không lỗi, không exit, cứ đứng im.
Nguyên nhân: Thường do depends_on vòng tròn (service A depends_on B, B depends_on A), hoặc một service exit code 0 nhưng compose chờ nó running. Một nguyên nhân khác: tty: true kèm stdin_open: true khiến Compose attach stdin và không exit.
Debug:
# Kiểm tra trạng thái từng service
docker compose ps
# Chạy với verbose log
docker compose --verbose up
# Kiểm tra vòng lặp depends_on
docker compose config --services
Fix: Nếu depends_on loop, tái cấu trúc: dùng health check + depends_on với condition: service_healthy thay cho condition: service_started (Compose v3 không hỗ trợ condition trong depends_on – cần dùng docker compose plugin). Nếu tty gây treo, bỏ tty: true và dùng -d flag.
# Chạy detached để tránh treo
docker compose up -d
Vấn đề 14: File ghi trong container bị mất sau restart
Triệu chứng: App ghi file (upload, cache, database) vào /app/data, container restart xong thì file biến mất.
Nguyên nhân: File được ghi vào writable layer của container, không phải volume. Khi container bị xóa (kể cả docker compose down), writable layer bị xóa theo. Container restart (docker restart) thì giữ được, nhưng recreate (stop + rm + run mới) thì mất.
Debug:
# Kiểm tra xem thư mục có được mount từ volume không
docker inspect <container> --format '{{json .Mounts}}' | jq
# Nếu output rỗng -- không có mount nào, đồng nghĩa file nằm trong writable layer
Fix:
# Mount volume hoặc bind mount cho thư mục cần persist
docker run -v app_uploads:/app/uploads ...
# Trong compose:
services:
app:
volumes:
- app_uploads:/app/uploads
volumes:
app_uploads:
Vấn đề 15: Container dùng quá nhiều CPU
Triệu chứng: Một container ngốn 100% CPU (thậm chí nhiều core), làm chậm toàn bộ host. docker stats hiển thị CPU % gần 100%.
Nguyên nhân: Không set CPU limit, container có thể dùng toàn bộ CPU host. Thường gặp: infinite loop trong code, process pool không giới hạn worker, hoặc cron job chạy sai interval.
Debug:
# Realtime CPU monitoring
docker stats --no-stream
# Xem process trong container
docker exec <container> top
# Trace syscall để tìm process gây cao CPU
docker exec <container> strace -c -p 1
Fix:
# Set CPU limit khi run (0.5 core)
docker run --cpus="0.5" ...
# Trong compose:
services:
app:
deploy:
resources:
limits:
cpus: '0.50'
# Nếu là Java/Node/Python app, kiểm tra GC log hoặc event loop blocking
Vấn đề 16: Không thể xóa image – “image is referenced”
Triệu chứng: docker rmi <image> báo lỗi:
Error: conflict: unable to remove repository reference "<image>"
(must force) - container <id> is using its referenced image
Nguyên nhân: Có container (kể cả stopped) vẫn đang reference image đó. Docker không cho xóa image khi còn container tham chiếu tới nó, dù container đã stopped.
Debug:
# Liệt kê tất cả container (cả stopped) dùng image
docker ps -a --filter "ancestor=<image>" --format "table {{.ID}}\t{{.Names}}\t{{.Status}}"
Fix:
# Xóa container stopped trước
docker container prune
# Rồi xóa image
docker rmi <image>
# Hoặc force (xóa cả reference -- container stopped vẫn còn đó, nhưng không còn tag tham chiếu)
docker rmi -f <image>
# Cleanup toàn bộ
docker system prune -a
Vấn đề 17: ENV variable không được inject
Triệu chứng: App đọc biến môi trường (ví dụ DATABASE_URL) nhưng giá trị rỗng hoặc sai, mặc dù đã set trong Dockerfile hoặc Compose file.
Nguyên nhân: Ba lỗi phổ biến: (1) Dùng shell form CMD làm mất biến môi trường, (2) Compose variable substitution $VARIABLE bị shell host expand trước khi vào container, (3) .env file không được load vì sai path hoặc sai naming.
Debug:
# In toàn bộ env trong container
docker exec <container> env
# So sánh với env trên host
docker exec <container> env | grep DATABASE
Fix:
# Luôn dùng exec form trong Dockerfile
CMD ["node", "server.js"] # Đúng: exec form
# CMD node server.js # Sai: shell form, có thể mất signal & env
# Trong Compose, escape $ bằng $$ nếu muốn literal
environment:
VAR: "$$HOME" # $$HOME -> literal $HOME trong container
# Hoặc dùng env_file
services:
app:
env_file:
- .env.production
Vấn đề 18: docker exec không vào được container
Triệu chứng: docker exec -it <container> bash báo lỗi:
OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found in $PATH
Nguyên nhân: Container không có bash (hoặc shell nào cả). Distroless image, scratch-based image, hoặc Alpine (chỉ có sh, không có bash) đều gây lỗi này. Hoặc process PID 1 đã exit – container đang ở trạng thái stopped.
Debug:
# Thử các shell khác
docker exec -it <container> sh
docker exec -it <container> ash # BusyBox/Alpine
docker exec -it <container> /bin/sh
# Kiểm tra container còn sống không
docker ps --filter "name=<container>"
Fix:
# Với Alpine-based image, dùng sh thay vì bash
docker exec -it <container> sh
# Với distroless: không có shell. Debug bằng cách khác:
# - docker logs
# - docker cp file vào/ra
# - docker inspect để xem config
# - Nếu cần debug, rebuild image với tag debug có shell
# Nếu container đã stopped:
docker start <container> # start lại trước khi exec
Vấn đề 19: Bind mount file thay đổi không reflect trong container
Triệu chứng: Sửa file config.yaml trên host, nhưng container vẫn thấy nội dung cũ. Restart container mới nhận được thay đổi mới.
Nguyên nhân: Khi bạn mount một file (không phải thư mục), nếu editor trên host (Vim, VS Code) ghi file bằng cách tạo file mới + rename (atomic save), inode của file thay đổi. Bind mount trong Docker trỏ đến inode cũ – container vẫn thấy nội dung cũ từ inode cũ.
Debug:
# Kiểm tra inode file host
ls -i /host/config.yaml
# Kiểm tra inode file trong container
docker exec <container> ls -i /app/config.yaml
# Nếu khác nhau -> inode thay đổi
Fix: Mount thư mục thay vì mount file đơn lẻ:
# Sai: mount file
docker run -v /host/config.yaml:/app/config.yaml ...
# Đúng: mount thư mục chứa file
docker run -v /host/config:/app/config ...
docker restart <container> sau mỗi lần cập nhật file, hoặc dùng config management tool thay vì bind mount cho production configuration.Vấn đề 20: Registry authentication fail
Triệu chứng: docker push hoặc docker pull từ private registry báo:
unauthorized: authentication required
Hoặc:
Error response from daemon: Get https://registry.example.com/v2/: denied
Nguyên nhân: Token hết hạn (AWS ECR token sống 12 tiếng, GCR 1 tiếng), credential helper bị sai config, hoặc docker login chưa được chạy / credential đã expire.
Debug:
# Kiểm tra credential đã lưu
docker info --format '{{json .}}' | jq '.RegistryConfig'
# Thử login lại
docker login registry.example.com
# Với AWS ECR:
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <account>.dkr.ecr.us-east-1.amazonaws.com
# Với Docker Hub:
docker login
Fix: Set up credential helper để auto-refresh token:
// ~/.docker/config.json
{
"credHelpers": {
"<account>.dkr.ecr.us-east-1.amazonaws.com": "ecr-login",
"us-central1-docker.pkg.dev": "gcloud"
}
}
# Cài credential helper
# AWS:
sudo apt install amazon-ecr-credential-helper
# GCP:
gcloud auth configure-docker us-central1-docker.pkg.dev
Tổng kết
20 vấn đề trên là những thứ mình gặp nhiều nhất khi chạy Docker trong production. Điểm chung: hầu hết đều có thể phòng tránh bằng config đúng ngay từ đầu – set resource limit, mount volume, viết Dockerfile đúng thứ tự, và dùng healthcheck.
Một vài nguyên tắc nhớ nhanh khi debug Docker:
- Luôn bắt đầu từ log:
docker logs --tail 100là lệnh đầu tiên. - Exit code nói rất nhiều: 137 = OOM kill, 1 = app error, 0 = normal exit (nhưng có thể không như mong đợi).
- Container là stateless: mọi dữ liệu cần tồn tại qua restart phải nằm trong volume.
- Limit resource: luôn set CPU/memory limit cho container production.
- Healthcheck + restart policy là cặp bài trùng: healthcheck phát hiện lỗi, restart policy hành động.
Bài tiếp theo (Bonus): Phần 16: Tối ưu Dockerfile: từ lý thuyết đến benchmark, systematic approach 5 bước tối ưu image từ 1.4GB xuống 87MB, kèm benchmark từng kỹ thuật.