Tôi dựng một daemon riêng rồi làm cho nó bừa đúng như một máy chủ sau vài tháng: ba lần build đè lên cùng một nhãn, mấy container đã dừng, volume mồ côi có tên lẫn ẩn danh.

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          6         1         237.5MB   237.5MB (100%)
Containers      4         1         52.43MB   52.43MB (100%)
Local Volumes   4         2         120.6MB   52.43MB (43%)
Build Cache     9         0         105B      105B

Rồi chạy từng lệnh prune một, và đo dung lượng thật của /var/lib/docker trước và sau mỗi lệnh — chứ không tin con số lệnh tự báo.

Lệnh Nó báo thu hồi /var/lib/docker thật
docker container prune -f 52,43 MB 415 → 365 MB
docker image prune -f 0 B 365 → 365 MB
docker builder prune -af 125,8 MB 365 → 285 MB
docker volume prune -f 15,73 MB 285 → 270 MB

Hai dòng ở giữa và dòng cuối đều có chuyện.

Lệnh prune nào chạm vào vùng nào, và hai cái bẫy

Bẫy 1: image prune xoá thật nhưng không giải phóng gì

Trước lệnh đó có 2 image dangling. Sau lệnh, còn 0. Nó đã xoá thật. Nhưng đĩa không trống thêm một byte nào.

Lý do nằm ở lệnh ngay sau: builder prune thu hồi 125,8 MB — chính là những lớp mà hai image kia dùng. Xoá bản ghi image không xoá được lớp, vì build cache vẫn đang giữ chúng.

Hệ quả thực dụng: nếu bạn chạy docker image prune, thấy Total reclaimed space: 0B rồi kết luận "máy sạch rồi", bạn đã bỏ qua vùng lớn nhất. Trên máy CI build vài chục lần mỗi ngày, build cache thường là thứ chiếm nhiều đĩa nhất.

docker builder prune -af              # xoa toan bo
docker builder prune -f --filter until=168h   # chi giu 7 ngay gan nhat

Bộ lọc until là lựa chọn tốt cho cron: cache gần đây vẫn giúp build nhanh, cache của tháng trước thì không.

Bẫy 2: volume prune bỏ qua mọi volume có tên

Lệnh báo thu hồi 15,73 MB. Nhưng còn lại:

v-dang-dung     <- dang duoc dung, dung ra khong duoc xoa
v-mo-coi-1      <- mo coi, 25 MB
v-mo-coi-2      <- mo coi, 25 MB

Hai volume mồ côi có tên vẫn nguyên. Chỉ volume ẩn danh bị xoá. Chạy lại với -a:

Total reclaimed space: 52.43MB
con lai: v-dang-dung

Vậy lệnh mặc định lấy được 15,73 MB trong tổng số 68,16 MB thật sự thu hồi được — bỏ sót 77%.

Đây là hành vi cố ý và hợp lý: volume có tên là thứ bạn đặt tên, nên Docker cho rằng bạn có ý định giữ nó. Nhưng nếu không biết, bạn sẽ tưởng mình đã dọn xong.

docker volume ls -qf dangling=true     # xem truoc cai gi se mat
docker volume prune -af                # roi moi xoa

Luôn liệt kê trước khi xoá. Một volume mồ côi có thể là dữ liệu CSDL của container bạn vừa docker rm để tạo lại — nó "mồ côi" đúng vài giây, và prune trong khoảng đó là mất sạch.

docker system prune không đụng tới volume

Đây là điều bảo vệ bạn khỏi đúng tai nạn trên. docker system prune gộp container + image + network + build cache, nhưng cố ý bỏ qua volume. Muốn xoá volume phải khai thêm:

docker system prune -a --volumes     # nghi ky truoc khi go co nay

Cờ -a cũng đáng cân nhắc riêng: không có nó, image prune chỉ xoá image không nhãn; có nó, mọi image không được container nào dùng đều bị xoá — kể cả image bạn vừa pull về để dùng ngày mai. Trên máy có mạng chậm, đó là vài chục phút tải lại.

Còn một vùng không lệnh nào chạm tới

Sau khi chạy hết mọi lệnh prune, tôi đo lại và vẫn thấy một chỗ phình. Đó là nhật ký container:

53.469.925 byte   .../0c0e4914...-json.log
docker system df bao cot Containers: 0B

Năm mươi ba megabyte, và mọi công cụ kế toán của Docker đều báo 0. Không prune nào xoá nó, vì nó thuộc về một container đang chạy — nó chỉ biến mất khi container bị xoá.

Phần 29 đã đo kỹ chuyện này: mặc định json-file không xoay vòng, nên tệp lớn mãi. Cách duy nhất là cấu hình trước, không phải dọn sau:

{ "log-driver": "local", "log-opts": { "max-size": "10m", "max-file": "3" } }

Một lịch dọn dẹp dùng được

Cho máy CI hoặc máy chủ build, chạy hằng tuần:

docker container prune -f --filter until=24h
docker image prune -af --filter until=168h
docker builder prune -f --filter until=168h
docker volume ls -qf dangling=true    # chi LIET KE, xoa bang tay sau khi xem

Ba dòng đầu an toàn để tự động vì chúng chỉ đụng tới thứ đã cũ và không ai dùng. Dòng cuối cố ý không tự động — dữ liệu không tạo lại được thì không nên phó cho cron.

Trên máy làm việc hằng ngày, chỉ cần nhớ một lệnh khi ổ đầy:

docker builder prune -af

Nó gần như luôn là vùng lớn nhất, và mất mát duy nhất là lần build sau chậm hơn.

Thử ba mươi giây

Xem bạn đang có gì trước khi xoá gì:

docker system df
echo "--- volume mo coi:"; docker volume ls -qf dangling=true
echo "--- log lon nhat:"
for c in $(docker ps -q); do
  p=$(docker inspect "$c" --format '{{.LogPath}}')
  [ -f "$p" ] && printf '%8s  %s\n' "$(du -h "$p"|cut -f1)" "$(docker inspect "$c" --format '{{.Name}}')"
done | sort -rh | head -5

Ba con số cần đọc theo thứ tự: Build Cache thường lớn nhất và xoá an toàn nhất; volume mồ côi phải xem tên trước khi xoá; nhật ký không xoá được bằng prune, phải cấu hình lại.

Phần sau đo tiếp: docker system df báo một con số, thư mục thật lại là con số khác — chênh ở đâu.