“kaç saattir post yok” mesajı geldi
Akşam saatlerinde fark ettim — saatlik content-generate cron’um sabahtan beri tek başarılı çalıştırma yapamamış. Pipeline-health monitor henüz state-change mailini atmamıştı (4 saat eşik dolmamış), ama GitHub Actions paneli kıpkırmızı.
Son başarılı çalışma 2026-05-04 12:11 UTC’de bitmiş, üzerinden 5+ saat geçmiş, 0 yeni içerik. Bu blog’un en sık aşağı düşme nedeni kaynak yetersizliği — disk veya RAM. İlk hangi olduğunu hızlıca buldum.
Run log’larında dikkat çeken satır:
##[error] System.IO.IOException: No space left on device
: '/home/github-runner/runner-mustafaerbay/_diag/pages/...log'
Runner kendi log dosyasını yazamıyor — diskte yer yok. Bu noktada validate adımına bile geçmemiş, runner’ın kendi _diag katmanı ölmüş. Cron başına tekrar tekrar deneniyor ama her seferinde aynı yerde patlıyor.
VPS’e SSH at, gör
$ ssh vps 'df -h /'
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 72G 72G 11M 100% /
72 GB diskte 11 MB serbest. Bir log satırı bile yazılamaz.
İkinci sorgu daha enteresan:
$ ssh vps 'sudo du -hx --max-depth=2 / 2>/dev/null | sort -hr | head -10'
72G /
54G /var
39G /var/lib
15G /var/www
7.2G /home
5.3G /home/github-runner
4.0G /usr
2.9G /opt
2.6G /usr/lib
2.3G /opt/mustafaerbay
/var/lib 39 GB. /var/www 15 GB. Bu bir kişisel blog VPS’i değil, üzerinde 6 farklı proje var. Gözüm /var/lib’e takıldı çünkü orada Docker var.
$ ssh vps 'sudo du -hx --max-depth=1 /var/lib | sort -hr | head'
39G /var/lib
38G /var/lib/docker ← BURASI
169M /var/lib/dkms
164M /var/lib/Acronis
140M /var/lib/apt
6.3M /var/lib/mustafaerbay
Docker tek başına 38 GB.
Docker’in iç dökümünü çıkar
$ ssh vps 'sudo docker system df'
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 33 9 27.5GB 23.27GB (84%)
Containers 13 13 1.192MB 0B (0%)
Local Volumes 8 8 387MB 0B (0%)
Build Cache 388 0 7.695GB 7.695GB
Bu tablo direkt cevap veriyor:
- 33 image var, sadece 9 tanesi aktif. 24’ü “kullanılmıyor ama silinmemiş” durumda. 23.27 GB reclaimable.
- 388 build cache layer’ı var, 0’ı aktif. Tamamı (7.7 GB) silinebilir.
- Container’lar ve volume’lar normal — onları boşaltmak istemiyorum (postgres data falan var).
Toplam silinebilir: ~31 GB. Sadece bunu boşaltsam diskte yeterli yer açılır.
Önce çalışan container’ları görelim, sonra kes
Dur acele etme. docker system prune -a her şeyi siler — çalışan ile çalışmayan image arasındaki sınırı bilmek lazım. docker ps çıktısına göz attım: aynı VPS’te yarım düzine farklı projenin container’ları var — kendi yan ürünlerim ve birkaç müşteri projesi. Toplam 13 sağlıklı container: postgres’ler, redis’ler, Next.js app’leri, bir Astro SSR servisi ve birkaç worker. Reclaim edebilecekleri sadece ANCHORLU OLMAYAN image’ler — yani çalışan bir container’ın referans etmediği eski image versiyonları.
İki güvenli komut sıraladım:
# 1. Build cache (eski layer'lar, hiç birşey kullanmıyor)
sudo docker builder prune -af
# 2. Unused images (çalışan container'a anchor'lı olmayanlar)
sudo docker image prune -af
-a flag’i tag’lı ama dangling olmayan image’leri de siler. Riskli mi? Bence değil, çünkü aktif container’a anchor’lı olanlar zaten silinmez (Docker reference count tutar). Sadece “bir zamanlar build edildi, kullanıldı, sonra yeni versiyon geldi” durumundaki eski image’ler gider.
Sonuç:
=== Docker build cache ===
Total reclaimed space: 33.48GB
=== Docker unused images ===
Total reclaimed space: 22.62GB
=== After ===
/dev/sda1 72G 40G 33G 56% /
56 GB reclaim ettim. Disk %100 → %56. 33 GB serbest alan. 13 container hepsi çalışmaya devam ediyor.
Şimdi: bunu otomatik yapalım
Bu olay üçüncü kez yaşanmıyor — bana ders olsun:
“İki kez yaşandığında bir manuel düzeltme yap. Üçüncüsünde otomatize et.”
Hazırladığım disk-cleanup.sh script’i basit ama dikkatli. Birkaç prensip:
#!/usr/bin/env bash
set -euo pipefail
echo "=== disk-cleanup başlıyor ==="
echo "before: $(df -h / | tail -1)"
# 1) Docker build cache > 72h (yeni cache hayatta kalır)
echo "-- docker builder prune (>72h)"
docker builder prune -af --filter "until=72h"
# 2) Dangling docker images (no -a — tagged-but-unused KORUNUR)
echo "-- docker image prune (dangling only)"
docker image prune -f
# 3) journal > 7d
journalctl --vacuum-time=7d
# 4) APT cache (regenerable)
apt-get clean
# 5) mustafaerbay dist-old (deploy backup, regenerated each deploy)
[ -d /opt/mustafaerbay/dist-old ] && rm -rf /opt/mustafaerbay/dist-old
# 6) GitHub runner _diag log dosyaları > 14d (sadece dosya, dizinleri DOKUNMA)
find /home/github-runner -path '*/_diag/*' -type f -name '*.log' -mtime +14 -delete
echo "after: $(df -h / | tail -1)"
Daily timer’a bağladım, 03:30 UTC’de çalışıyor:
[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=10m
Persistent=true
RandomizedDelaySec=10m — sistemde başka 03:30 cron’ları varsa aynı anda çakışmasın diye. Persistent=true — VPS reboot ettiyse kaçırılan sefer çalışır.
Sonuç: tek satır neden?
Diskim doldu çünkü Docker zerre temizlik yapmaz. Her docker-compose build yeni bir image oluşturur, eskisini referenced bırakmaz ama silmez de. Birkaç ay sonra bir yıl sonra disk patladığında “wow yapay zeka mı bu kadar büyüdü” dersin.
Aslında kimse büyütmüyor. Docker hoarder. Aktif değilsen disk yangını sana bunu öğretiyor.
İki saatlik bir manuel kurtarma + bir saatlik systemd timer kurulumu = bir daha bunu yaşamayacağım garantisi. Asıl ders bu: incident’i sonraki seferinin altyapısı yap.
Yarın disk-cleanup.timer ilk seferini koşacak. Bekliyorum.