SSD Nodes Learn 🎉 VPS $5.50/월부터
가이드 Matt Connor작성자 Matt Connor

Docker 디스크 용량 확보 및 prune 명령어 가이드

VPS 디스크 공간이 부족할 때 Docker 이미지, 컨테이너, 빌드 캐시, 볼륨 점유 현황을 확인하고 안전하게 정리하는 방법을 설명합니다. 데이터 손실 없이 불필요한 레이어와 로그 파일을 삭제하여 디스크 여유 공간을 확보하는 최적의 순서를 확인하십시오.

정리 작업을 시작하기 전에 디스크 공간 점유 현황 파악하기

Docker는 VPS에서 이미지, 중지된 컨테이너, 빌드 캐시, 로컬 볼륨이라는 네 가지 영역의 디스크 공간을 점유합니다. 먼저 docker system df을 실행하여 어떤 영역이 공간을 차지하는지 확인한 뒤, 해당 영역만 정리하는 최소한의 prune 명령을 실행하십시오. 이 가이드의 마지막 명령인 docker volume prune -a은 데이터를 삭제하며 되돌릴 수 없으므로 순서가 중요합니다.

Docker가 아닌 파일 시스템부터 확인하십시오.

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

df는 디스크 사용 현황의 심각도를 알려줍니다. du은 구체적인 점유 위치를 보여줍니다. -x 플래그를 사용하면 du가 단일 파일 시스템 내에서만 동작하므로, 마운트된 별도 볼륨을 따라가며 중복 계산하는 일을 방지합니다. 여기서 중요한 디렉터리는 다섯 곳입니다. overlay2은 이미지와 컨테이너 레이어를, volumes은 볼륨 데이터를, containers은 컨테이너 메타데이터와 로그 파일을, buildkit는 빌드 캐시를, image은 레이어 메타데이터를 보관합니다.

sudo과 셸 와일드카드 사용 시 주의할 점이 있습니다. 많은 사용자가 이 부분에서 시간을 낭비합니다. /var/lib/docker는 root 소유이므로 일반 사용자는 읽을 수 없으며, 따라서 ls /var/lib/dockerPermission denied를 반환합니다. sudo du -sh /var/lib/docker/*와 같은 명령도 실패하는데, 이는 sudo이 실행되기 전에 셸이 *을 먼저 확장하려 시도하고, 셸 자체는 해당 디렉터리를 읽을 권한이 없기 때문입니다. 아래의 모든 명령은 이러한 이유로 와일드카드 대신 find이나 --max-depth를 사용합니다.

이제 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

위 수치는 특정 장비의 결과일 뿐이며 사용자의 환경과는 무관합니다. 수치보다는 구성 형태를 파악하십시오. TOTAL은 객체 수를, ACTIVE은 현재 사용 중인 객체 수를 나타내며, RECLAIMABLE는 prune 명령을 통해 확보할 수 있는 예상 공간을 의미합니다.

RECLAIMABLE과 관련하여 사용자가 자주 혼동하는 두 가지 사항이 있습니다. 첫째, 공유 이미지 레이어를 해당 레이어를 사용하는 모든 이미지마다 합산하므로, 이미지 항목에 표시된 예상 확보 공간은 실제보다 크게 나타나는 경우가 많습니다. 둘째, Docker는 로그 파일을 회수 가능한 객체로 간주하지 않으므로 이 수치에 로그 파일은 포함되지 않습니다. du가 보고하는 디렉터리 크기가 docker system df의 결과보다 훨씬 크다면 그 원인은 로그 파일이며, 이에 대한 내용은 아래 섹션에서 다룹니다.

객체별 상세 내역을 보려면 -v을 추가하십시오.

docker system df -v

이 명령은 요약 정보를 객체 유형별로 분리하여 보여줍니다. 이미지 섹션에는 SHARED SIZEUNIQUE SIZE 열이 추가되어 개별 이미지가 실제로 차지하는 용량을 확인할 수 있습니다. 볼륨 섹션에는 LINKS 항목이 추가되는데, 이는 해당 볼륨에 연결된 컨테이너의 개수입니다. LINKS을 기억하십시오. 이 값이 0이어야만 볼륨 prune 명령의 대상이 됩니다.

Dangling 이미지와 사용되지 않는(unused) 이미지의 차이

이 두 용어는 혼용되기 쉽지만 서로 다른 개념입니다. 대상 객체가 다르기 때문에 필터의 동작 방식도 차이가 있습니다.

Dangling 이미지는 태그가 없는 이미지를 의미합니다. <none> 명령을 실행하면 docker images에 표시됩니다. 이미지를 다시 빌드할 때마다 이 현상이 발생합니다. docker build -t myapp:latest . 명령은 myapp:latest 태그를 새 이미지로 옮기는데, 이때 이전 이미지는 모든 레이어를 유지하지만 이름은 잃게 됩니다. 이를 참조하는 객체가 없으며, 시스템이 스스로 정리하지도 않습니다.

사용되지 않는(unused) 이미지는 태그 여부와 관계없이 현재 어떤 컨테이너도 참조하지 않는 모든 이미지를 의미합니다. 지난달에 pull 받았지만 현재 실행 중이지 않은 postgres:16는 사용되지 않는 이미지이며, 이는 dangling 상태가 아닙니다.

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

두 번째 질문이 먼저 나옵니다.

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

해당 프롬프트를 주의 깊게 읽으십시오. "Associated to them(연관된)"이라는 표현은 실행 중이거나 정지된 기존 컨테이너 객체를 의미합니다. 만약 docker compose down을 실행했다면 컨테이너는 삭제된 상태이므로, 해당 서비스가 사용하던 모든 이미지는 이제 사용되지 않는 상태가 되며 -a은 이 이미지들을 모두 삭제합니다. 복구할 수 없는 데이터가 손실되는 것은 아니지만, 다음 docker compose up -d 실행 시 모든 이미지를 다시 pull 하거나 빌드해야 하므로 소규모 VPS에서는 대역폭과 빌드 시간이 낭비됩니다. 이것이 바로 무언가를 prune 하기 전에 docker compose down이 삭제하는 것과 stop이 남겨두는 것을 미리 알아두어야 하는 실질적인 이유입니다.

필터를 사용하면 최근 이미지를 범위에서 제외할 수 있습니다.

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

이 명령은 240시간(10일) 이전에 생성된 사용되지 않는 이미지만 삭제하고, 그보다 최신인 이미지는 그대로 둡니다. until 값은 240h과 같은 Go duration 문자열이나 2026-08-01T00:00:00과 같은 절대 타임스탬프를 인자로 받습니다.

빌드 캐시의 정의와 무제한으로 증가하는 이유

BuildKit은 Docker Engine 23.0 버전부터 docker builddocker compose build에서 기본으로 사용하는 빌드 도구입니다. 이 도구는 실행하는 모든 Dockerfile의 각 단계 결과를 캐싱하며, 해당 캐시를 /var/lib/docker/buildkit에 저장합니다. 두 번째 빌드가 수 초 만에 끝나는 이유는 이 캐시 덕분이며, 따라서 캐시는 제 역할을 수행하고 있는 것입니다. 문제는 기본 설정상 오래된 항목을 만료시키는 기능이 없다는 점입니다. 매번 변경되는 COPY 단계를 포함한 이미지를 50번 빌드하면, 50개의 레이어 세트가 그대로 유지됩니다.

빌드 캐시는 docker image prune에서는 보이지 않습니다. 이는 별도의 명령어를 사용하는 독립적인 객체 유형입니다.

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

이 명령어들은 이미지나 데이터에 영향을 주지 않습니다. 빌드 캐시를 삭제할 때 발생하는 유일한 비용은 다음 빌드가 한 번 느리게 실행된다는 점뿐입니다. 이미지를 주기적으로 다시 빌드하는 VPS 환경에서 Build Cache은 종종 docker system df에서 가장 큰 용량을 차지하므로, 삭제해도 안전한 가장 큰 항목입니다.

안전한 순서부터 파괴적인 순서로 나열한 prune 명령어

이 목록을 위에서부터 차례대로 실행하며 df -h /가 정상으로 돌아오면 즉시 중단하십시오. 모든 명령어는 완료 시 Total reclaimed space: 줄을 출력합니다.

  1. docker container prune은 중지된 컨테이너를 삭제합니다. 컨테이너의 쓰기 가능 레이어도 함께 삭제되므로, 볼륨 외부의 컨테이너 내부에 기록된 모든 데이터는 삭제됩니다. 볼륨은 영향을 받지 않습니다.
  2. docker image prune는 댕글링(dangling) 이미지(이름이 없는 이미지)만 삭제합니다. 이는 이미지 관련 명령어 중 가장 안전합니다.
  3. docker builder prune은 댕글링 빌드 캐시를 삭제합니다. 이 작업의 대가는 빌드 속도가 한 번 느려지는 것입니다.
  4. docker image prune -a는 어떤 컨테이너도 참조하지 않는 모든 이미지를 삭제합니다. 이 작업 후에는 이미지를 다시 pull하거나 빌드해야 할 수 있습니다.
  5. docker system prune는 앞의 세 가지 작업을 한 번에 수행하며 사용하지 않는 네트워크까지 삭제합니다.
  6. docker volume prune은 사용하지 않는 익명 볼륨을 삭제합니다.
  7. docker volume prune -a은 명명된 볼륨을 포함하여 사용하지 않는 모든 볼륨을 삭제합니다. 이 명령어는 데이터베이스를 삭제할 수 있습니다.

docker system prune은 실행 전 자신의 작업 범위를 명시합니다.

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를 추가하면 익명 볼륨이 작업 범위에 포함됩니다. -a을 추가하면 이미지 삭제 범위가 댕글링 이미지에서 사용되지 않는 모든 이미지로 확대됩니다. 운영 환경의 호스트에서 docker system prune -a --volumes -f을 전체 실행하는 것은 공간을 확보하려다 데이터를 잃게 되는 주된 원인입니다.

볼륨 정리(pruning)가 데이터베이스를 삭제하는 이유

이 섹션은 두 번 읽어야 합니다.

컨테이너가 연결되어 있지 않으면 볼륨은 사용되지 않는 것으로 간주됩니다. 이것이 판단 기준의 전부입니다. Docker는 볼륨이 비어 있는지, compose 파일에 여전히 선언되어 있는지, 혹은 데이터베이스의 유일한 복사본인지 확인하지 않습니다. LINKS 0docker system df -v에 있다는 것은 정리 대상이라는 뜻일 뿐, 그 이상도 이하도 아닙니다.

이제 두 가지 일반적인 작업을 연속으로 수행해 보겠습니다. 스택을 깔끔하게 재시작하기 위해 docker compose down를 실행합니다. 이 명령은 컨테이너를 제거하고 명명된 볼륨(named volume)은 그대로 남겨두는데, 이는 문서에 명시된 대로 정확히 동작하는 것입니다. 이제 Postgres 볼륨은 아무 곳에도 연결되지 않은 상태가 됩니다. 10분 뒤 공간을 확보하려고 docker volume prune -a를 실행하면 데이터베이스는 사라집니다. 두 명령 모두 올바르게 동작했지만, 이 순서대로 실행하면 데이터가 파괴됩니다.

Docker Engine 23.0(API 버전 1.42)부터는 기본 명령의 범위가 이전보다 좁아졌습니다.

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

익명 볼륨(anonymous volume)은 이미지가 VOLUME을 선언했으나 사용자가 이름을 지정하지 않았을 때 Docker가 자동으로 생성한 볼륨입니다. 이런 볼륨은 보통 보관할 필요가 없는 데이터를 담고 있습니다. compose 파일에 직접 작성한 명명된 볼륨은 -a을 추가할 때만 제거됩니다. 이전 버전의 Docker는 기본 명령으로 두 유형을 모두 제거했으므로, 업그레이드된 환경에서 과거의 습관을 그대로 믿어서는 안 됩니다. 이 차이는 명명된 볼륨과 바인드 마운트의 차이를 이해해야 명확해집니다. 바인드 마운트는 Docker 볼륨이 아니므로 어떤 정리 명령으로도 삭제되지 않기 때문입니다.

삭제하기 전에 확인하십시오. myapp_pgdata을 확인하려는 볼륨 이름으로 바꾸어 실행하십시오.

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

볼륨에 적용된 dangling=true 필터는 '비어 있음'이 아니라 '참조되지 않음'을 의미합니다. _data을 나열하면 실제 내부 내용을 볼 수 있습니다. 그 안에 pgdata이나 mysql 디렉터리가 있다면, 더 진행하기 전에 멈추고 복사본을 만드십시오. compose 파일에 선언된 모든 볼륨을 아무런 확인 절차 없이 제거하는 docker compose down -v을 통해서도 동일한 데이터 파괴가 발생합니다.

볼륨은 Docker 호스트에서 재빌드로 복구할 수 없는 유일한 요소입니다. 그렇기 때문에 볼륨 데이터는 오타가 섞인 명령어가 닿을 수 없는 서버 외부에서 실행되는 restic 백업에 보관해야 합니다.

정리되지 않는 파일: 컨테이너 로그 파일

모든 항목을 정리했고 docker system df 명령으로도 회수할 공간이 거의 없는데 디스크가 여전히 가득 차 있다면 로그 파일을 확인해야 합니다.

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

모든 컨테이너는 표준 출력과 표준 에러를 /var/lib/docker/containers/ 경로 아래의 JSON 파일에 기록합니다. 기본 설치 상태에서는 max-size 설정이 지정되어 있지 않아 제한이 없으므로, 크래시 루프에 빠진 컨테이너 하나가 파티션이 가득 찰 때까지 로그를 기록할 수 있습니다. 컨테이너가 실행 중인 상태에서는 로그 파일이 생성되고 있으므로, 어떤 정리 명령으로도 이 파일들을 삭제할 수 없습니다.

파일을 직접 삭제하지 마십시오. 열려 있는 로그 파일에 rm 명령을 실행해도 공간은 확보되지 않습니다. Docker 데몬이 파일 디스크립터를 계속 점유하고 있으며, 해당 핸들이 닫히기 전까지 커널이 블록을 할당된 상태로 유지하기 때문입니다. df 명령을 사용해도 디스크 사용량은 전혀 줄어들지 않습니다. 대신 파일을 잘라내어(truncate) 동일한 inode를 유지하면서 데몬이 계속 기록할 수 있도록 해야 합니다.

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

이는 임시 방편일 뿐입니다. 해당 컨테이너에 대해 docker logs 명령을 실행하면 즉시 결과가 나타나지 않으며, 파일은 다시 즉시 커지기 시작합니다. 근본적인 해결책은 다음 섹션에서 다룰 로그 로테이션입니다.

매번 작업 전후를 측정하십시오

무엇이 prune 작업을 수행했는지 추측하지 마십시오. 측정값을 기록하고, 명령어를 한 번 실행한 뒤, 다시 측정값을 기록하십시오.

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

두 개의 df 출력값을 비교하십시오. 이 수치만이 서버의 지속적인 서비스 여부를 결정하는 유일한 기준입니다. docker system df은 실제로 이동한 행을 알려주며, 각 prune 작업은 고유한 Total reclaimed space: 수치를 출력합니다.

df은 변하지 않았는데 docker system df에서 공간이 확보되었다고 나온다면, 열려 있는 파일 핸들이 삭제된 블록을 점유하고 있는 상태이며 이는 앞서 언급한 로그 파일 문제입니다. 두 수치 모두 변했음에도 하루 안에 디스크가 다시 가득 찬다면, 이는 정리 문제가 아니라 증가 문제이므로 로그 로테이션과 스케줄링된 작업을 통해 해결해야 합니다.

디스크 용량 부족 재발 방지 방법

로그 크기 제한. /etc/docker/daemon.json 파일을 생성하거나 편집합니다.

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

이 설정은 각 컨테이너의 로그 크기를 30 MB로 제한합니다. log-opts 아래의 모든 값은 숫자형이라도 반드시 문자열로 작성해야 합니다. 잘못된 형식의 daemon.json 파일은 Docker 데몬의 시작을 방해하고 모든 컨테이너를 중단시키므로, 재시작 전에 구문 오류가 없는지 확인하십시오.

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

이제 docker info 명령을 실행하면 Logging Driver: json-file가 출력되어야 합니다. 제한 설정 자체는 재시작 이후 생성된 컨테이너의 docker inspectLogConfig 섹션에서 확인할 수 있습니다. 중요한 점은 이 설정이 새로 생성되는 컨테이너에만 적용된다는 것입니다. 기존 컨테이너는 생성 당시의 설정을 유지하므로, 변경 사항을 적용하려면 컨테이너를 다시 생성해야 합니다.

docker compose up -d --force-recreate

특정 서비스에서 발생하는 로그가 너무 많다면, compose 파일 내에서 서비스별로 제한을 설정하는 것이 더 나은 선택입니다.

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

주기적인 정리 작업 예약. 매주 dangling 이미지와 오래된 빌드 캐시를 대상으로 정리 작업을 수행합니다. -a--volumes 옵션을 스케줄 작업에 포함하지 마십시오. 스택이 중단된 상태에서 작업이 실행되면 해당 스택의 이미지가 삭제될 수 있으며, --volumes 옵션이 포함된 경우 데이터까지 삭제될 위험이 있습니다.

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

마지막 줄은 스크립트를 수동으로 한 번 실행하여 자동화 전에 출력을 확인하는 용도입니다. 파일은 실행 권한이 있어야 하며, 이름에 점(.)이 포함되어서는 안 됩니다. run-parts는 실행 권한이 없거나 확장자가 있는 파일을 건너뛰기 때문입니다.

여유 공간 알림 설정. 디스크가 가득 찬 후 수행하는 정리는 복구 작업일 뿐입니다. 80퍼센트 지점에서 알림을 받는 것이 예방입니다.

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

이 명령을 기존에 사용하는 알림 도구와 함께 cron에 등록하십시오. 여유 공간 확인만으로는 부족하므로, VPS 디스크 상태 모니터링과 병행하십시오. 디스크 결함과 용량 부족은 모두 컨테이너를 중단시키지만, 해결 방법은 서로 다르기 때문입니다.

위의 모든 내용은 데이터 루트가 /var/lib/docker인 표준 설치 환경을 가정합니다. 만약 daemon.jsondata-root 키를 사용하여 경로를 변경했다면, 모든 명령어에서 해당 경로를 수정하여 사용하십시오. 새로운 서버에서 이러한 구조를 올바르게 잡는 것은 VPS에 Docker 설정하기의 일부이며, 40 GB의 컨테이너가 잘못된 파티션에 쌓이기 전에 결정하는 것이 훨씬 수월합니다.

FAQ

docker system prune 명령을 실행하면 볼륨도 삭제됩니까?

아니요. 기본 명령은 중지된 컨테이너, 사용하지 않는 네트워크, 이름 없는(dangling) 이미지, 사용하지 않는 빌드 캐시만 제거하며, 확인 메시지에서 해당 항목들을 정확히 나열합니다. 볼륨은 --volumes 옵션을 추가할 때만 대상이 되며, Docker Engine 23.0 버전부터는 이 옵션이 이름이 지정된 볼륨이 아닌 익명 볼륨에만 적용됩니다. 이름이 지정된 볼륨은 docker volume prune -adocker compose down -v 명령으로 삭제됩니다. 이 두 명령을 사용할 때는 주의해야 합니다.

docker prune을 실행했는데도 디스크가 여전히 가득 차 있는 이유는 무엇입니까?

주로 두 가지 원인이 있습니다. 첫 번째는 /var/lib/docker/containers/ 경로에 있는 컨테이너 로그 파일입니다. 어떤 prune 명령도 이 파일을 건드리지 않으며, max-size 설정을 하지 않으면 용량이 무한정 늘어납니다. 두 번째는 프로세스가 여전히 열어두고 있는 삭제된 파일입니다. 컨테이너가 실행 중일 때 rm로 로그를 삭제하면 데몬이 파일 디스크립터를 유지하므로 커널이 블록을 해제하지 않으며, 따라서 df 결과에도 변화가 없습니다. sudo du -xh --max-depth=1 /var/lib/dockerdocker system df을 비교하여 어떤 경우에 해당하는지 확인하십시오.

docker image prune과 docker image prune -a의 차이점은 무엇입니까?

기본 명령은 태그를 잃어버린, 즉 거의 항상 재빌드 과정에서 발생한 이름 없는(dangling) 이미지만 제거합니다. -a 옵션을 사용하면 기존 컨테이너가 참조하지 않는 모든 이미지를 제거하며, 여기에는 사용자가 의도적으로 가져온(pull) 태그가 지정된 이미지도 포함됩니다. docker compose down를 실행하면 컨테이너가 삭제되므로, -a은 해당 스택의 이미지까지 함께 제거합니다. 다음 시작 시 다시 가져오거나 빌드하므로 영구적으로 손실되는 것은 없지만, 네트워크 속도가 느리면 긴 대기 시간이 발생할 수 있습니다.

Docker 로그가 디스크를 채우지 않게 하려면 어떻게 해야 합니까?

/etc/docker/daemon.json 파일의 log-opts 아래에 max-sizemax-file를 설정한 뒤, sudo systemctl restart docker로 데몬을 재시작하십시오. 이 설정은 재시작 이후 생성된 컨테이너에만 적용되므로, 실행 중인 컨테이너는 docker compose up -d --force-recreate으로 다시 생성해야 합니다. 특정 서비스의 로그가 다른 서비스보다 훨씬 많이 쌓이는 경우, compose 파일의 logging 키 아래에 동일한 두 옵션을 설정하는 것이 좋습니다.

cron 작업으로 docker system prune을 실행해도 안전합니까?

기본 docker system prune -f 명령은 모든 스택이 항상 실행 중인 호스트에서는 안전하지만, 중지된 컨테이너를 제거하므로 나중에 다시 시작하려고 의도적으로 중지해 둔 컨테이너까지 삭제할 수 있습니다. 더 안전한 예약 작업은 docker image prune -fdocker builder prune -f --filter until=168h을 조합하는 것이며, 이는 볼륨을 건드리지 않으면서 가장 빠르게 증가하는 두 영역을 정리합니다. -a이나 --volumes는 절대 예약 작업으로 등록하지 마십시오.