← 글 목록

notes

Docker - auto clean

Docker 미사용 이미지·컨테이너 자동 정리와 디스크 관리 메모.

Jenkins로 빌드 → SSH로 보내고 → 서버에서 docker build를 반복하다 보면 이미지가 쌓여 디스크가 찬다. pipeline 끝에 prune을 넣어도 되지만, 배포 중이거나 실패 재시도할 때 타이밍이 애매해서 systemd timer로 매일 새벽에 한 번 돌리는 쪽을 택했다.

  • 빌드 서버에 쌓이는 이미지를 systemd timer로 매일 새벽 3시에 정리한다.
  • service가 prune을 한 번 돌고, timer가 그 service를 매일 실행한다. 결과는 journalctl로 본다.
  • --volumes는 멈춘 compose 스택의 DB 볼륨까지 지울 수 있어서, DB가 있는 서버에서는 뺀다.

뭘 지우는 명령인가

docker system prune -a --volumes -f
  • 중지된 컨테이너
  • 사용 중이지 않은 네트워크
  • dangling이 아닌 안 쓰는 이미지까지 (-a)
  • 안 쓰는 볼륨 (--volumes)

-f는 확인 질문 없이 바로 실행. 타이머/cron에 넣을 때 필요하다.

헷갈렸던 점: “사용 중”은 지금 떠 있는 컨테이너가 참조하는가 기준이다. compose 스택을 내려 둔 상태면 named volume도 prune 대상이 될 수 있다. DB 볼륨이 있는 서버면 --volumes를 빼거나, 볼륨은 수동으로만 지우는 편이 낫다.

systemd oneshot + timer

cron도 되지만, 서버에 이미 systemd가 있으니 서비스/타이머 파일로 관리하는 게 상태 확인(list-timers)이 편했다.

제일 크게 느낀 차이는 두 가지다. cron은 서버가 꺼져 있던 시각의 작업을 그냥 건너뛰는데 timer는 Persistent=true면 다음 부팅 때 한 번 돌려 준다. 그리고 로그를 따로 리다이렉트하지 않아도 journalctl에 남는다.

1) 서비스

sudo vi /etc/systemd/system/docker-cleanup.service
[Unit]
Description=Docker System Cleanup Service
After=docker.service

[Service]
Type=oneshot
ExecStart=/usr/bin/docker system prune -a --volumes -f
User=root

[Install]
WantedBy=multi-user.target

oneshot = 한 번 돌고 끝. 타이머가 주기적으로 이 서비스를 start 한다.

2) 타이머

sudo vi /etc/systemd/system/docker-cleanup.timer
[Unit]
Description=Run Docker Cleanup Daily

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target

자정 대신 새벽 3시로 둔 이유: 배포/배치가 자정 근처에 겹치는 날이 있어서. Persistent=true는 서버가 꺼져 있던 날이면 다음에 한 번 보정해 준다.

간격을 쓰고 싶으면 OnUnitActiveSec=24h도 가능하다.

3) 활성화

sudo systemctl daemon-reload
sudo systemctl enable --now docker-cleanup.timer
sudo systemctl status docker-cleanup.timer
sudo systemctl list-timers | grep docker

수동으로 한 번 돌려 보기:

sudo systemctl start docker-cleanup.service
sudo journalctl -u docker-cleanup.service -n 50

로그를 파일로 남기고 싶을 때

[Service]에:

StandardOutput=append:/var/log/docker-cleanup.log
StandardError=append:/var/log/docker-cleanup-error.log

디렉터리·권한만 먼저 만들어 두면 된다.

ExecStart를 고르는 기준

처음엔 제일 공격적인 명령으로 시작했다가, DB가 같이 있는 서버에서는 보통 단계로 내렸다. 배포 후 문제가 생겨 이전 이미지로 돌아가야 하는 서버는 기간 필터를 줘서 최근 것은 남겨 둔다.

기간 필터를 준 service 파일

ExecStart는 여러 줄 쓸 수 있고 위에서부터 차례로 돈다. 중간에 하나가 실패하면 거기서 멈추니, 실패해도 괜찮은 줄은 앞에 -를 붙인다.

[Service]
Type=oneshot
ExecStart=/usr/bin/docker container prune -f --filter "until=72h"
ExecStart=/usr/bin/docker image prune -a -f --filter "until=72h"
ExecStart=-/usr/bin/docker builder prune -f --filter "until=72h"
User=root

until 필터는 volume prune에는 못 쓴다. 볼륨은 이 서비스에서 아예 뺄다.

운영하면서 느낀 점

  • prune은 빌드 캐시·옛 태그를 지워 디스크를 크게 먹는다. 대신 다음 빌드는 캐시 없이 처음부터라 느려질 수 있다. 매일 -a --volumes가 과하면 docker image prune -a만 / docker builder prune만으로 줄인다.
  • 프로덕션에서 볼륨까지 자동 삭제하는 건 위험 부담이 크다. 필요하면 ExecStart를 docker system prune -a -f로 바꾸고 volumes는 별도 정책.
  • 권한: docker 그룹 유저로 돌릴 거면 User=와 소켓 권한을 맞출 것.

cron으로 기간 지정해 지우는 쪽은 “Docker - cron …” 글에 따로 적어 두었다. 여기는 systemd로 주기 돌리기만 정리.