Три часа ночи, мониторинг будит дежурного: раздел / занят на 98 %, MySQL не пишет binlog, CI падает на docker pull с no space left on device. Подключаемся, запускаем df -h — на корне осталось 412 МБ. Это не взлом, а три месяца без контроля над логами, слоями Docker, кэшем apt и journal systemd, которые постепенно съели 40-гигабайтный VPS.
Такой сценарий типичен для небольших VPS: скромный диск, контейнеры круглосуточно, постоянные pull образов и непрерывное логирование. Ручной rm -rf медленный и опасный; рабочая схема — сначала найти «тяжёлых», затем пакетно очистить каталоги, которые можно восстановить. В этой статье — скрипт очистки, который мы регулярно запускаем в продакшене, с чёткими пометками: что можно автоматизировать, а что требует ручной проверки.
Предпосылки: SSH и sudo. Если подключиться невозможно (например, ключ SSH не записывается), начните с нашего гайда по устранению отказа SSH на Linux-сервере, чтобы отличить переполнение диска от сети или файрвола. Прежде чем менять правила UFW, освободите место — сервисам нужна возможность писать логи.
Диск полон: что ломается первым?
Когда корневая ФС заполнена, симптомы кажутся случайными: SSH ещё работает, но vim не сохраняет, apt install падает, Docker не поднимает контейнеры, Nginx отдаёт 500, а error.log перестаёт расти. MySQL и PostgreSQL уходят в read-only или падают; systemd перезапускает сервисы в цикле — не создать pid-файл или socket. Cron молча проваливается, когда не может дописать лог.
Первый шаг — смотреть точки монтирования, а не угадывать «большую папку». Отдельный том /data может быть полупустым, пока корень забит; без df -hT легко уйти не туда:
# Свободное место по разделам df -hT # Крупнейшие каталоги первого уровня под / (может быть медленно) sudo du -xh --max-depth=1 / 2>/dev/null | sort -hr | head -20 # Исчерпание inode тоже выглядит как «полный диск» df -ih
du на хостах с миллионами файлов может идти долго. Если машина уже тормозит, проверяйте подозрительные пути точечно: sudo du -sh /var/lib/docker /var/log /var/cache. В руководстве du(1) описаны -x (не переходить на другие ФС) и --apparent-size — не дайте bind mount или отдельным томам ввести вас в заблуждение.
Частые «пожиратели» диска: справочная таблица
У каждого стека свой набор, но пути ниже постоянно всплывают на VPS Ubuntu и Debian. Web + Docker + CI на одном диске 40 ГБ — знакомая формула: journal и слои контейнеров растут в фоне, а прикладные логи взлетают при пиках трафика. В таблице отмечено, можно ли безопасно очистить скриптом — строки «осторожно» требуют проверки зависимостей перед удалением.
| Путь / источник | Типичный объём | Авто-безопасно? | Примечания |
|---|---|---|---|
/var/lib/docker |
От нескольких ГБ до десятков ГБ | Частично | Висячие образы, остановленные контейнеры, неиспользуемые volumes; не трогать data volumes работающих контейнеров |
/var/log/journal |
1–10+ ГБ | Да | journal systemd быстро раздувается без лимита размера |
/var/log/*.log |
Сотни МБ — ГБ | Осторожно | Truncate или logrotate; прямое rm может оставить открытые fd и «висячие» inode |
/var/cache/apt |
Сотни МБ — несколько ГБ | Да | apt-get clean безопасен — удаляет только скачанные .deb |
/tmp, /var/tmp |
Разный | Частично | Удалять файлы без доступа 7+ дней; следите за временными загрузками приложений |
Nginx proxy_cache |
Несколько ГБ | Да | Кэш пересобирается; если вы настраивали cache по нашему гайду по оптимизации reverse proxy Nginx, ожидайте кратковременных MISS |
Старые ядра в /boot |
Сотни МБ | Да | apt autoremove --purge удаляет старые kernel; оставьте текущий и предыдущий |
| Core dump, отчёты о сбоях | Разный | Да | /var/crash, файлы core.* приложений |
/home, /var/lib/mysql, монтирования object storage или каталоги бэкапов занимают больше всего — очистка кэша не спасёт. Нужна архивация, увеличение диска или перенос холодных данных. Скрипты возвращают восстановимое место; они не заменяют планирование ёмкости.
Скрипт очистки в один клик (готов к копированию)
Скрипт ниже по умолчанию в режиме dry-run: показывает, сколько места освободится, и какие команды выполнятся. Передайте --apply для реального удаления. Сначала dry-run, потом ручная проверка — мы уже видели, как команда пропускала этот шаг, prune недельный base-образ (единственная локальная копия выведенного сервиса) и час восстанавливала pipeline. Модули разделены: закомментируйте ненужное (Docker, если не установлен, или кэш Nginx без proxy_cache).
#!/usr/bin/env bash # Очистка диска сервера в один клик — только восстановимый кэш # Использование: sudo ./disk-cleanup.sh # предпросмотр # sudo ./disk-cleanup.sh --apply # выполнение set -euo pipefail APPLY=false [[ "${1:-}" == "--apply" ]] && APPLY=true log() { echo "[$(date '+%F %T')] $*"; } run() { if $APPLY; then log "EXEC: $*" eval "$@" else log "DRY-RUN: $*" fi } before=$(df -h / | awk 'NR==2 {print $3 " used, avail " $4}') log "Before: $before" # 1. кэш apt if command -v apt-get >/dev/null; then run "apt-get clean -y" run "apt-get autoremove -y --purge" fi # 2. journal systemd — оставить 7 дней или 500 МБ if command -v journalctl >/dev/null; then run "journalctl --vacuum-time=7d" run "journalctl --vacuum-size=500M" fi # 3. неиспользуемые ресурсы Docker (без бизнес-данных в named volumes) if command -v docker >/dev/null && docker info >/dev/null 2>&1; then run "docker system prune -af --filter 'until=168h'" run "docker builder prune -af --filter 'until=168h'" fi # 4. временные каталоги — без доступа 7+ дней run "find /tmp /var/tmp -type f -atime +7 -print 2>/dev/null | head -20" if $APPLY; then find /tmp /var/tmp -type f -atime +7 -delete 2>/dev/null || true fi # 5. Nginx proxy_cache (подставьте свой cache_path) NGINX_CACHE="/var/cache/nginx" if [[ -d "$NGINX_CACHE" ]]; then run "du -sh '$NGINX_CACHE'" run "find '$NGINX_CACHE' -type f -delete 2>/dev/null || true" fi # 6. дампы сбоев [[ -d /var/crash ]] && run "rm -rf /var/crash/*" # 7. кэш pip / npm (если есть) [[ -d /root/.cache/pip ]] && run "rm -rf /root/.cache/pip/*" for u in /home/*; do [[ -d "$u/.npm" ]] && run "npm cache clean --force --cache '$u/.npm' 2>/dev/null || rm -rf '$u/.npm/_cacache'" done after=$(df -h / | awk 'NR==2 {print $3 " used, avail " $4}') log "After: $after" $APPLY || log "Dry-run завершён. Когда готовы: sudo $0 --apply"
Права: sudo chmod 750 /usr/local/sbin/disk-cleanup.sh. При первом запуске без --apply сохраните вывод DRY-RUN. Если секция Docker показывает массовое удаление образов, убедитесь, что не удаляете единственную локальную копию, чей тег в registry уже снят.
docker system prune -a удаляет все неиспользуемые образы, заставляя полный re-pull при следующем деплое — больно на VPS с узким каналом. Безопаснее: --filter until=168h, чтобы уходили только недельные неиспользуемые слои, или регулярный docker image prune -f для dangling-образов. См. официальную документацию Docker по pruning.
По модулям: что удаляется, что нет
journalctl: --vacuum-time и --vacuum-size можно комбинировать; systemd обрезает бинарные журналы по документации journalctl. Для постоянного потолка задайте SystemMaxUse=500M в /etc/systemd/journald.conf, затем systemctl restart systemd-journald.
Файлы логов: для большого лога, который процесс ещё держит открытым, truncate -s 0 /var/log/nginx/access.log безопаснее, чем rm — процесс сохраняет fd, а удаление файла не освобождает место, пока процесс не перезапустится. Долгосрочно настройте logrotate, чтобы Nginx и приложения ротировали логи ежедневно с хранением N копий.
apt: apt-get clean очищает только /var/cache/apt/archives; autoremove --purge удаляет старые ядра и сиротские зависимости. После обновления kernel оставьте текущий и предыдущий — на случай, если новый не загрузится. На проде мы сверяем uname -r с dpkg -l 'linux-image-*' перед autoremove.
Бизнес-данные не трогать: /var/lib/mysql, /var/lib/postgresql, RDB Redis, каталоги загрузок пользователей и точки монтирования бэкапов — вне скрипта. Если du показывает их первыми, нужна архивация, расширение диска или холодное хранилище — не очистка кэша.
После очистки: не дать диску снова тихо заполниться
Снизить корень с 98 % до 60 % — только начало. Без ограничителей те же каталоги вернутся: journal снова вырастет до 8 ГБ за шесть недель, слои Docker накопятся после каждого деплоя, пик трафика повторит инцидент. Минимальный превентивный набор для маленького VPS — три пункта:
- Пороги алертов: облачная консоль или self-hosted Prometheus на 80 %, SMS или pager на 85 % — не ждите 95 %.
- Еженедельный cron:
0 4 * * 0 root /usr/local/sbin/disk-cleanup.sh --apply >> /var/log/disk-cleanup.log 2>&1 - Потолки journald + logrotate: превратить безграничный рост в управляемый потолок.
На Docker Compose задайте max-size и max-file для logging driver json-file — иначе stdout одного контейнера заполнит диск так же надёжно, как осиротевшие слои образов, и эту ловушку слишком часто игнорируют.
Чеклист перед вмешательством
Скопируйте в runbook и пройдите по порядку при следующем алерте:
df -hTиdf -ih— место или inode?du -shточечно: docker, journal, logs, cache- Dry-run скрипта → ручная проверка →
--apply - После очистки
df -h, затем проверка сервисов (DB, Nginx, Docker) - Добавить лимиты journald, logrotate, cron и алерты на 80 %
Полный диск — не «удалить пару файлов», а проверка ёмкости и политики логирования. Скрипт переводит вас от панического rm в 3 часа ночи к повторяемой процедуре. Зафиксируйте, что очистили и насколько сдвинулся df — однострочный постмортем часто выявляет пропущенный stanza logrotate. Остальное — профилактика, чтобы pager молчал, а следующий дежурный получил playbook, а не загадку.
Сборки съедают диск? Разделите роли
Если VPS одновременно крутит прод и локальные xcodebuild, многослойные Docker-сборки или кэш моделей Ollama, маленький диск исчезает за неделю компиляции — DerivedData и слои образов конкурируют с прод-логами на одном SSD.
Здоровее: прод-VPS держать лёгким, тяжёлые сборки — отдельно: облачный Mac для iOS-пакетов, выделенный CI runner, прод тянет только финальные артефакты.
Mac mini M4 с unified memory и низким idle-потреблением хорошо подходит как узел «только сборка, без 24/7 трафика». В паре с Linux VPS для runtime кэши компиляции перестают бороться с прод-логами за один диск. Если планируете такое разделение, сначала перенесите тяжёлое на облачный Mac — тарифы VPSSpark и меньше ночей с df -h в три часа.