SSD Nodes Learn 🎉 VPS từ $5.50/tháng
Hướng dẫn Matt ConnorBởi Matt Connor

Docker prune: giải phóng dung lượng đĩa trên VPS

VPS đầy đĩa vì Docker? Xem image, container, build cache hay volume đang chiếm chỗ, rồi chạy lệnh prune hẹp nhất để dọn mà không mất dữ liệu.

Xác định thành phần đang chiếm dung lượng đĩa trước khi dọn dẹp

Docker chiếm dung lượng đĩa trên VPS ở 4 nơi: image, container đã dừng, build cache và local volume. Chạy docker system df trước để xác định thành phần nào đang chiếm dung lượng, sau đó chạy lệnh prune hẹp nhất có thể giải phóng phần đó. Thứ tự rất quan trọng, vì lệnh cuối trong hướng dẫn này, docker volume prune -a, sẽ xóa dữ liệu và không thể hoàn tác.

Hãy bắt đầu với filesystem, không phải Docker.

df -h /
sudo du -xh --max-depth=1 /var/lib/docker | sort -h

df cho biết mức độ đầy của filesystem. du cho biết dung lượng đã nằm ở đâu. Flag -x giới hạn du trong cùng một filesystem, nên nó không đi theo mount vào volume riêng rồi đếm trùng. 5 thư mục cần chú ý ở đây: overlay2 chứa các image layer và container layer, volumes chứa dữ liệu volume, containers chứa metadata của container và log file, buildkit chứa build cache, còn image chứa metadata của layer.

Cần lưu ý về sudo và wildcard của shell, vì phần này thường làm nhiều người mất thời gian. /var/lib/docker thuộc sở hữu của root và user thông thường không thể đọc nó, nên ls /var/lib/docker trả về Permission denied. Lệnh như sudo du -sh /var/lib/docker/* cũng thất bại, vì shell của bạn mở rộng * trước khi sudo chạy, trong khi shell không thể đọc thư mục đó. Mọi lệnh bên dưới đều dùng find hoặc --max-depth thay cho wildcard vì lý do này.

Bây giờ hãy xem thông tin từ chính Docker.

docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          24        6         12.4GB    8.91GB (71%)
Containers      31        6         1.42GB    1.39GB (97%)
Local Volumes   14        5         6.03GB    4.11GB (68%)
Build Cache     212       0         9.87GB    9.87GB

Các số liệu đó lấy từ một máy cụ thể và không cho biết gì về máy của bạn. Hãy xem cấu trúc của số liệu. TOTAL đếm số object, ACTIVE đếm số object hiện đang được sử dụng, còn RECLAIMABLE là ước tính của Docker về dung lượng có thể giải phóng từ dòng đó bằng prune.

Có 2 điểm về RECLAIMABLE thường gây nhầm lẫn. Lệnh này đếm các image layer dùng chung một lần cho mỗi image sử dụng chúng, nên dòng image thường cho thấy dung lượng có thể giải phóng lớn hơn thực tế. Lệnh này cũng không bao giờ bao gồm container log file, vì Docker không xem log file là object có thể thu hồi. Khi du báo một thư mục lớn hơn nhiều so với số liệu docker system df, nguyên nhân là log file; phần bên dưới sẽ đề cập đến vấn đề này.

Thêm -v để xem chi tiết theo từng object.

docker system df -v

Lệnh này chia phần tóm tắt thành một mục cho từng loại object. Mục image thêm các cột SHARED SIZEUNIQUE SIZE, giúp bạn biết chính xác một image chiếm bao nhiêu dung lượng. Mục volume thêm số lượng LINKS, tức số container đang gắn với volume đó. Hãy nhớ LINKS, vì giá trị 0 là điều kiện duy nhất để các lệnh volume prune được áp dụng.

Ảnh không có tag và ảnh không được sử dụng

Hai thuật ngữ này không thể dùng thay cho nhau. Các filter hoạt động khác nhau vì chúng áp dụng cho các object khác nhau.

Ảnh không có tag là ảnh không có tag. Ảnh này hiển thị dưới dạng <none> trong docker images. Mỗi lần rebuild, bạn sẽ tạo ra một ảnh như vậy: docker build -t myapp:latest . chuyển tag myapp:latest sang ảnh mới, còn ảnh cũ vẫn giữ toàn bộ layer nhưng mất tên. Không có gì tham chiếu đến ảnh đó và cũng không có cơ chế nào tự dọn dẹp ảnh đó.

Ảnh không được sử dụng là bất kỳ ảnh nào, có tag hoặc không, mà hiện không có container nào tham chiếu đến. Một postgres:16 mà bạn đã pull tháng trước nhưng hiện không chạy là ảnh không được sử dụng, không phải ảnh không có tag.

docker image prune        # dangling images only
docker image prune -a     # every image no container refers to

Lệnh thứ hai sẽ hỏi trước.

WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N]

Đọc kỹ prompt đó. “Associated to them” nghĩa là một container object hiện có, đang chạy hoặc đã dừng. Nếu bạn chạy docker compose down, các container sẽ bị xóa, nên mọi ảnh mà các service đó đã sử dụng đều trở thành ảnh không được sử dụng, và -a sẽ xóa tất cả các ảnh đó. Bạn không mất dữ liệu nào không thể lấy lại, nhưng lần docker compose up -d tiếp theo sẽ pull lại hoặc rebuild toàn bộ, làm tốn bandwidth và thời gian build trên VPS nhỏ. Đây là một lý do thực tế để biết docker compose down xóa những gì và stop để lại những gì đang chạy trước khi prune.

Filter có thể loại các ảnh mới ra khỏi phạm vi xử lý.

docker image prune -a --filter "until=240h"

Lệnh này xóa các ảnh không được sử dụng đã được tạo hơn 240 giờ (10 ngày) trước và giữ lại các ảnh mới hơn. Giá trị until nhận chuỗi duration theo định dạng Go như 240h hoặc một timestamp tuyệt đối như 2026-08-01T00:00:00.

Build cache là gì và vì sao tăng không giới hạn

BuildKit là builder mà Docker sử dụng mặc định cho docker builddocker compose build kể từ Docker Engine 23.0. Nó cache kết quả của mọi step trong mọi Dockerfile mà nó chạy và lưu cache đó tại /var/lib/docker/buildkit. Cache là lý do lần build thứ hai hoàn tất chỉ trong vài giây, nên nó đang hoạt động đúng mục đích. Vấn đề là mặc định không có gì tự xóa các entry cũ. Nếu build cùng một image 50 lần với một step COPY thay đổi sau mỗi lần build, bạn sẽ giữ lại 50 bộ layer.

Build cache không hiển thị trong docker image prune. Đây là một loại object riêng và có command riêng.

docker builder prune                        # dangling cache
docker builder prune -a                     # all unused cache
docker builder prune --filter until=168h    # cache untouched for 7 days

Không lệnh nào trong số này tác động đến image hoặc dữ liệu của bạn. Chi phí duy nhất khi xóa build cache là lần build tiếp theo sẽ chạy chậm một lần. Trên VPS thường xuyên rebuild image, Build Cache thường là mục lớn nhất trong docker system df, nên đây là thứ lớn và an toàn nhất để xóa.

Các lệnh prune, sắp xếp từ an toàn đến có tính phá hủy

Thực hiện lần lượt từ trên xuống và dừng ngay khi df -h / hoạt động ổn định trở lại. Mỗi lệnh đều in một dòng Total reclaimed space: khi hoàn tất.

  1. docker container prune xóa các container đã dừng. Writable layer của chúng cũng bị xóa, vì vậy mọi dữ liệu container ghi bên ngoài volume sẽ mất theo. Lệnh này không tác động đến volume.
  2. docker image prune chỉ xóa các image dangling. Đây là lệnh image an toàn nhất.
  3. docker builder prune xóa build cache dangling. Đổi lại, một lần build sẽ chạy chậm hơn.
  4. docker image prune -a xóa mọi image không được container nào tham chiếu. Đổi lại, bạn phải pull lại hoặc build lại image.
  5. docker system prune thực hiện 3 bước đầu cùng lúc và xóa thêm các network không dùng.
  6. docker volume prune xóa các anonymous volume không dùng.
  7. docker volume prune -a xóa mọi volume không dùng, bao gồm cả volume có tên. Đây là lệnh có thể xóa database.

docker system prune sẽ nêu phạm vi tác động của chính nó trước khi chạy.

WARNING! This will remove:
        - all stopped containers
        - all networks not used by at least one container
        - all dangling images
        - unused build cache
Are you sure you want to continue? [y/N]

Volumes được cố ý loại khỏi danh sách trên. Thêm --volumes sẽ đưa anonymous volume vào phạm vi xử lý. Thêm -a sẽ mở rộng bước xử lý image, từ chỉ image dangling sang mọi image không dùng. Chạy full docker system prune -a --volumes -f trên host production là cách khiến người dùng mất dữ liệu khi đang cố giải phóng dung lượng.

Vì sao prune volume lại xóa database

Đây là phần bạn nên đọc lại hai lần.

Một volume được xem là không được sử dụng khi không có container nào gắn vào nó. Đó là toàn bộ tiêu chí kiểm tra. Docker không kiểm tra volume có trống hay không, file compose còn khai báo volume đó hay không, hoặc volume có chứa bản sao duy nhất của database hay không. LINKS 0 trong docker system df -v nghĩa là có thể prune, và không có ý nghĩa nào khác.

Bây giờ hãy thực hiện hai thao tác thông thường liên tiếp. Bạn chạy docker compose down để restart stack một cách sạch sẽ. Lệnh này xóa các container và giữ nguyên các named volume, đúng như tài liệu mô tả. Postgres volume của bạn lúc này không còn được gắn vào container nào. Mười phút sau, bạn chạy docker volume prune -a để giải phóng dung lượng, và database biến mất. Cả hai lệnh đều hoạt động đúng. Chính chuỗi thao tác này đã làm mất dữ liệu.

Từ Docker Engine 23.0 (API version 1.42), lệnh mặc định có phạm vi hẹp hơn trước.

WARNING! This will remove anonymous local volumes not used by at least one container.

Anonymous volume là volume do Docker tự tạo, thường vì image khai báo VOLUME nhưng bạn không đặt tên cho volume. Các volume này thường chứa dữ liệu mà bạn không yêu cầu giữ lại. Named volume, tức loại volume bạn ghi trong file compose, chỉ bị xóa khi bạn thêm -a. Các phiên bản Docker cũ xóa cả hai loại bằng lệnh mặc định, vì vậy đừng dựa vào thói quen hình thành trên một máy đã được nâng cấp. Sự khác biệt này chỉ có ý nghĩa sau khi bạn biết named volume khác bind mount như thế nào, vì bind mount hoàn toàn không phải Docker volume và không lệnh prune nào có thể tác động đến nó.

Hãy kiểm tra trước khi xóa. Thay myapp_pgdata bằng tên volume bạn đang kiểm tra.

docker volume ls -f dangling=true
docker volume inspect myapp_pgdata
sudo ls -la /var/lib/docker/volumes/myapp_pgdata/_data

Filter dangling=true trên volume có nghĩa là không được tham chiếu, không phải trống. Liệt kê _data để xem nội dung thực tế bên trong volume. Nếu thấy thư mục pgdata hoặc mysql, hãy dừng lại và tạo một bản sao trước khi làm tiếp. Việc mất dữ liệu tương tự cũng xảy ra với docker compose down -v. Lệnh này xóa mọi volume được file compose khai báo mà không hỏi lại bạn.

Volume là thành phần duy nhất trên Docker host mà một lần rebuild không thể tạo lại. Vì vậy, dữ liệu trong volume phải được đưa vào một bản backup restic chạy ngoài server, nơi một flag gõ nhầm không thể tác động đến dữ liệu đó.

Khi không prune được gì: các file log của container

Bạn đã prune mọi thứ, docker system df hầu như không cho thấy gì có thể giải phóng, nhưng disk vẫn đầy. Hãy kiểm tra log.

sudo find /var/lib/docker/containers -name '*-json.log' -exec du -h {} + | sort -h | tail -10

Mỗi container ghi standard output và standard error vào một file JSON bên dưới /var/lib/docker/containers/. Trong cài đặt mặc định, max-size không được đặt, tức là không có giới hạn. Vì vậy, một container mắc trong crash loop có thể ghi cho đến khi partition đầy. Không lệnh prune nào xóa được các file này, vì các container tạo ra chúng vẫn đang chạy, nên theo định nghĩa chúng không thể bị prune.

Không xóa file đó. Chạy rm trên một file log đang mở không giải phóng dung lượng nào, vì Docker daemon vẫn giữ file descriptor đang mở và kernel tiếp tục giữ các block được cấp phát cho đến khi handle đó đóng. df sẽ không thay đổi. Thay vào đó, hãy truncate file. Cách này giữ nguyên inode và cho phép daemon tiếp tục ghi.

sudo find /var/lib/docker/containers -name '*-json.log' -exec truncate -s 0 {} +
df -h /

Đây chỉ là cách xử lý tạm thời. docker logs cho các container đó hiện không trả về gì, nhưng các file sẽ lập tức tăng kích thước trở lại. Cách xử lý đúng là cấu hình rotation, được trình bày trong phần tiếp theo.

Đo trước và sau, lần nào cũng vậy

Đừng đoán tác động của lệnh prune. Hãy đo một lần, chạy một lệnh, rồi đo lại.

df -h /
docker system df
docker builder prune -f --filter until=168h
docker image prune -f
docker system df
df -h /

So sánh hai kết quả df. Đây là con số duy nhất cho biết server của bạn có tiếp tục phục vụ được hay không. Sau đó, docker system df cho biết chính xác dòng nào đã thay đổi, còn mỗi lệnh prune sẽ in ra số liệu Total reclaimed space: riêng.

Nếu df không thay đổi nhưng docker system df cho biết đã giải phóng dung lượng, một file handle đang mở vẫn giữ các block đã bị xóa. Đây chính là vấn đề với file log được nêu ở trên. Nếu cả hai đều thay đổi nhưng disk lại đầy trong vòng một ngày, bạn đang gặp vấn đề tăng trưởng dữ liệu chứ không phải vấn đề dọn dẹp. Cách xử lý là cấu hình rotation và thêm scheduled job.

Cách ngăn ổ đĩa đầy trở lại

Giới hạn kích thước log. Tạo hoặc chỉnh sửa /etc/docker/daemon.json.

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

Thiết lập này giới hạn mỗi container ở 30 MB log. Mọi giá trị bên dưới log-opts đều phải là string, kể cả các giá trị dạng số. Kiểm tra file có parse được trước khi restart, vì daemon.json sai định dạng sẽ khiến daemon không khởi động được và kéo theo toàn bộ container dừng theo.

python3 -m json.tool /etc/docker/daemon.json
sudo systemctl restart docker
docker info | grep -i 'logging driver'

docker info lúc này phải báo Logging Driver: json-file. Các giới hạn sẽ xuất hiện trong section LogConfig của docker inspect trên container được tạo sau khi restart. Đây là chi tiết quan trọng: setting này chỉ áp dụng cho container mới. Container hiện có vẫn giữ cấu hình lúc được tạo, vì vậy hãy recreate chúng.

docker compose up -d --force-recreate

Bạn cũng có thể đặt cùng giới hạn này cho từng service trong file compose. Đây là lựa chọn phù hợp hơn khi một service tạo quá nhiều log cần có giới hạn riêng.

services:
  app:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Lập lịch prune có phạm vi hẹp. Chạy hàng tuần, chỉ áp dụng cho dangling image và build cache cũ. Không bao giờ đặt -a hoặc --volumes trong scheduled job, vì job chạy lúc một stack đang dừng sẽ xóa image của stack đó; còn với --volumes, nó sẽ bắt đầu xóa dữ liệu của bạn.

sudo tee /etc/cron.weekly/docker-prune >/dev/null <<'EOF'
#!/bin/sh
docker image prune -f
docker builder prune -f --filter until=168h
EOF
sudo chmod +x /etc/cron.weekly/docker-prune
sudo /etc/cron.weekly/docker-prune

Dòng cuối chạy script một lần bằng tay để bạn xem output trước khi script chạy unattended. File phải có quyền executable và tên không được chứa dấu chấm, vì run-parts bỏ qua mọi file không executable và mọi file có extension.

Cảnh báo khi dung lượng trống thấp. Chạy prune sau khi ổ đĩa đã đầy chỉ là xử lý sự cố. Cảnh báo khi dung lượng sử dụng đạt 80 phần trăm mới là cách phòng ngừa.

usage=$(df --output=pcent / | tr -dc '0-9')
[ "$usage" -ge 80 ] && echo "root filesystem at ${usage} percent full"

Đặt cấu hình đó vào cron và dùng notifier bạn đang có. Dung lượng trống chỉ là một nửa vấn đề, vì vậy hãy kết hợp cảnh báo này với giám sát tình trạng ổ đĩa trên VPS của bạn. Ổ đĩa hỏng và ổ đĩa đầy đều khiến container dừng, nhưng hai vấn đề này cần cách xử lý khác nhau.

Toàn bộ phần trên giả định bạn cài đặt theo cấu hình tiêu chuẩn, với data root tại /var/lib/docker. Nếu bạn đã chuyển data root bằng key data-root trong daemon.json, hãy thay path của bạn vào mọi command. Thiết lập đúng layout ngay trên một máy mới là một phần của việc thiết lập Docker trên VPS, và quyết định việc này trước khi có 40 GB container nằm trên partition không phù hợp sẽ dễ hơn nhiều.

FAQ

docker system prune có xóa volume của tôi không?

Không. Lệnh cơ bản xóa các container đã dừng, network không dùng, image mồ côi và build cache không dùng. Prompt xác nhận cũng liệt kê chính xác nhóm này. Volume chỉ được đưa vào phạm vi xử lý khi thêm --volumes. Từ Docker Engine 23.0, flag đó áp dụng cho volume ẩn danh, không áp dụng cho volume có tên. Volume có tên bị xóa bởi docker volume prune -adocker compose down -v. Đây là hai lệnh cần đặc biệt cẩn thận.

Vì sao disk vẫn đầy sau khi chạy docker prune?

Có hai nguyên nhân thường gặp. Nguyên nhân thứ nhất là file log của container trong /var/lib/docker/containers/. Không lệnh prune nào đụng đến các file này, và chúng sẽ tăng không giới hạn cho đến khi bạn cấu hình max-size. Nguyên nhân thứ hai là một file đã bị xóa nhưng vẫn đang được process mở. Nếu bạn xóa log bằng rm trong khi container vẫn đang chạy, daemon vẫn giữ file descriptor. Kernel không giải phóng các block, nên df không báo thay đổi. So sánh sudo du -xh --max-depth=1 /var/lib/docker với docker system df để xác định trường hợp của bạn.

Sự khác nhau giữa docker image prunedocker image prune -a là gì?

Lệnh cơ bản chỉ xóa image mồ côi, tức các image đã mất tag, gần như luôn do một lần build lại. Dạng -a xóa mọi image mà không có container hiện tại nào tham chiếu, bao gồm cả các image có tag mà bạn đã chủ động pull. Sau docker compose down, các container đã bị xóa, nên -a cũng sẽ xóa các image của stack đó. Không có gì bị mất vĩnh viễn, vì lần start tiếp theo sẽ pull hoặc build lại chúng. Tuy nhiên, nếu đường truyền chậm thì quá trình này sẽ mất nhiều thời gian.

Làm thế nào để log của Docker không làm đầy disk?

Đặt max-sizemax-file trong log-opts của /etc/docker/daemon.json, sau đó restart daemon bằng sudo systemctl restart docker. Setting này chỉ áp dụng cho các container được tạo sau lần restart đó. Vì vậy, hãy tạo lại các container đang chạy bằng docker compose up -d --force-recreate. Bạn có thể đặt cùng hai option cho từng service trong file compose, dưới key logging. Cách này phù hợp khi một service tạo log nhiều hơn đáng kể so với các service còn lại.

Chạy docker system prune trong cron job có an toàn không?

docker system prune -f cơ bản an toàn trên host nơi mọi stack luôn chạy. Tuy nhiên, lệnh này xóa các container đã dừng. Vì vậy, nó sẽ xóa cả container mà bạn đã chủ động dừng và dự định khởi động lại sau. Cron job an toàn hơn là docker image prune -f kèm docker builder prune -f --filter until=168h. Cách này giải phóng hai nhóm dữ liệu tăng nhanh nhất và không thể đụng đến volume. Không bao giờ lập lịch chạy -a hoặc --volumes.