В GitLab CI тема «storage» почти всегда всплывает не в момент настройки, а когда внезапно заканчивается место: Runner перестаёт стартовать job’ы, Docker падает на pull/overlay2, а GitLab раздувается из‑за артефактов и отчётов. При этом cache и artifacts — ключевые инструменты ускорения сборок, просто ими нужно управлять как ресурсом: сроками жизни, ключами, лимитами и уборкой.
Ниже — практический разбор: чем отличаются cache и artifacts, как работает expire_in, что такое cache:key и runner cache, где это хранится и как делать cleanup так, чтобы не «убить скорость» и не потерять нужные файлы.
Cache vs artifacts: что хранить и зачем
GitLab CI даёт два похожих по виду механизма, но с разной логикой и последствиями для диска. Если перепутать их назначение, вы получите либо вечный рост хранилища GitLab (из‑за артефактов), либо непредсказуемые сборки (из‑за конфликтующего кэша).
Cache: ускоряем повторяющиеся шаги
Cache предназначен для переиспользования между пайплайнами и/или ветками: зависимости, кэши менеджеров пакетов, промежуточные каталоги компилятора. Он сильнее всего влияет на скорость, потому что экономит скачивания и пересборки.
- Обычно не является «результатом», который нужно скачивать пользователю.
- Может быть общим между job’ами и пайплайнами при совпадении ключей.
- Его удаление почти безрисковое: максимум — пайплайн станет медленнее.
Artifacts: переносим результаты между стадиями и сохраняем доказательства
Artifacts — это выходные файлы job’ы: собранные архивы, отчёты тестов, coverage, JUnit, результаты сканирования и т.д. Они нужны либо следующим стадиям, либо людям (скачать сборку), либо GitLab (показать отчёты в UI).
- Привязаны к конкретной job’е/пайплайну.
- Часто используются в деплое или для расследования инцидентов.
- Слишком раннее удаление может ломать релизный процесс и диагностику.
Простое правило: всё, что можно восстановить автоматически (зависимости, кэши компилятора), — это cache. Всё, что является результатом работы пайплайна и нужно «как факт», — artifacts, но с осмысленным сроком жизни.
Где именно растёт диск: GitLab vs Runner
Прежде чем «чистить всё», важно понять, где заканчивается место: на сервере GitLab или на машинах GitLab Runner. Симптомы похожие (ошибки записи, падение job’ов), но лечение разное.
Хранилище GitLab: artifacts и отчёты
В self-managed GitLab артефакты живут на стороне GitLab (файловое хранилище или объектное — зависит от конфигурации). Даже если Runner отдельный, artifacts всё равно «утекают» в GitLab-storage.
Что чаще всего раздувает GitLab:
- длинные сроки хранения artifacts без
expire_in; - артефакты из веток и merge request’ов, которые никто не чистит;
- логи/отчёты, сохраняемые «на всякий случай» в каждом пайплайне;
- параллельно может расти registry/packages, но это отдельный контур контроля.
Runner cache и рабочие директории: диск заканчивается внезапно
Runner cache хранится локально на машине с GitLab Runner (shell executor) или в директориях/томах Docker executor — зависит от настроек. Это частая причина «no space left on device»: кэш копится быстро, а автоматическая чистка обычно не настроена.
Отдельный «пожиратель диска» у Docker Runner — слои образов и build cache (overlay2). Формально это не GitLab cache, но в реальности заканчивается тот же диск.
Если вы держите Runner на отдельной машине, удобный и предсказуемый вариант — выделить под CI отдельный сервер или VDS с запасом по диску и возможностью быстро расширить хранилище.

expire_in: главный рычаг против вечного хранения artifacts
Параметр expire_in относится к artifacts и задаёт срок, после которого GitLab пометит артефакты как просроченные и сможет их удалить (в рамках фоновых задач и настроек очистки).
Рабочая практика по срокам:
- для отчётов тестов, линтеров и временных логов — 1–7 дней;
- для сборок, которые иногда нужно скачать/откатить — 7–30 дней (по процессу);
- «длинные» сроки оставляйте только для релизов по тегам или отдельных каноничных пайплайнов.
Пример: отчёты на 3 дня
test:
stage: test
script:
- ./run-tests.sh
artifacts:
when: always
expire_in: 3 days
paths:
- reports/
reports:
junit: reports/junit.xml
Так сохраняется диагностическая ценность для MR и разборов падений, но отчёты не копятся месяцами.
Cache key: как не превратить кэш в свалку (и не убить скорость)
cache:key определяет, какие job’ы разделяют один и тот же кэш. Частая ошибка — ключ слишком уникальный (например, включает SHA коммита): кэш никогда не переиспользуется и только растёт. Обратная ошибка — ключ слишком общий: кэш конфликтует между ветками/версиями зависимостей и сборка начинает падать «странно».
Три рабочие стратегии ключей
- По lockfile: лучший баланс. Меняется lockfile — меняется кэш, иначе переиспользуется.
- По ветке: удобно для долгоживущих веток, но мусорит на feature-ветках.
- По версии runtime/инструмента: добавляйте версию Node/Go/Java, чтобы избежать несовместимостей.
Пример: кэш по lockfile
build:
image: node:20
script:
- npm ci
- npm run build
cache:
key:
files:
- package-lock.json
paths:
- .npm/
- node_modules/
Ключ на основе lockfile автоматически «инвалидирует» кэш при изменении зависимостей. Для репозиториев с большим количеством веток это почти всегда лучше, чем ключ по имени ветки или коммиту.
policy: pull-push и контроль разрастания
Политика кэша определяет, будет ли job только скачивать кэш или ещё и обновлять его. Часто разумно разделить роли:
- в «установочных» job’ах —
policy: pull-push; - в остальных —
policy: pull, чтобы не плодить вариации кэша.
Artifacts по делу: меньше файлов, правильные paths и когда не сохранять
Storage нередко «съедают» не большие архивы, а тысячи мелких файлов (например, зависимости). Их не стоит складывать в artifacts «чтобы передать на следующую стадию»: для этого есть cache или правильная сборочная стратегия (пересборка в чистом окружении, либо один job, который делает и build, и упаковку).
Как уменьшить artifacts без потери смысла
- Сохраняйте только то, что реально нужно: итоговый архив/пакет, отчёты, несколько ключевых логов.
- Избегайте временных каталогов сборщика и полного workspace.
- Если артефакт нужен только для деплоя — делайте минимальный пакет, а не «всё собранное дерево».
Пример: артефакт только с билдом
build:
stage: build
script:
- make build
artifacts:
expire_in: 7 days
paths:
- dist/app.tar.gz
Если параллельно в инфраструктуре поднимаете внутренние сервисы и панели для артефактов/логов, не забывайте про базовую гигиену: для веб-интерфейсов и GitLab корректно настроенный TLS обычно обязателен. При необходимости можно выпустить и обновлять SSL-сертификаты централизованно.
Cleanup storage: что чистить, где и как не сломать пайплайны
Уборка — это два параллельных процесса: на стороне GitLab (просроченные artifacts) и на стороне Runner (runner cache, рабочие директории, docker data). Ошибка — пытаться «одной командой» вылечить всё.
Проверка: что именно занимает место
Начните с банального, но обязательного: посмотрите топ потребителей диска. На Runner и на GitLab это будут разные директории.
df -h
sudo du -xhd1 /var | sort -h
sudo du -xhd1 /home | sort -h
Если Runner в Docker — проверьте docker storage:
docker system df
Runner cache: безопасная чистка и «скользящее окно»
С точки зрения надёжности кэши можно удалять смело: вы не теряете «истину», вы теряете только ускорение. Чтобы скорость не просела резко, чистите по принципу «скользящего окна»: удаляйте старое, а не всё подряд.
- Держите кэши на отдельном разделе или диске (проще контролировать и расширять).
- Ограничьте срок жизни файлов в кэше через cron или systemd-tmpfiles.
- Не запускайте агрессивную чистку в пиковое время на «горячих» runner’ах, иначе получите шторм скачиваний.
Docker на Runner: мусор слоёв и неиспользуемых образов
Если executor — Docker, внезапное заполнение диска чаще происходит из‑за образов, контейнеров и build cache. В этом случае cleanup — это регулярная уборка Docker.
docker image prune -af
docker builder prune -af
docker container prune -f
docker volume prune -f
Перед агрессивной чисткой убедитесь, что volumes не используются постоянно какими-то сервисами. Удаление build cache и образов увеличит время сборок до тех пор, пока кэш заново не прогреется.

Как ускорять pipeline без бесконтрольного роста диска
Цель — не «закэшировать всё», а закэшировать самое дорогое и стабильное, а остальное собирать заново за предсказуемое время. По смыслу это очень похоже на подходы к кешированию в вебе: важна стратегия и срок жизни, а не максимальный объём. Если хотите глубже разобраться в подходах к TTL и контролю кеша на уровне инфраструктуры, пригодится материал про TTL и контроль доступа в кеше Nginx.
Чек-лист практик
- Cache — для зависимостей и стабильных кэшей инструментов (npm/pip/composer/go build cache).
- Ключи — lockfile плюс версия runtime/образа.
- Не кэшируйте целиком workspace и нестабильный build output без необходимости.
- Artifacts — только то, что реально нужно скачать/передать/показать.
- Всегда задавайте
expire_in, кроме осознанно «долгих» релизных случаев. - Регулярный cleanup на Runner: docker prune и/или чистка каталога runner cache.
Пример .gitlab-ci.yml: сбалансированный cache + artifacts
Шаблон для Node.js: кэшируем зависимости, артефакты держим коротко и только нужные.
stages:
- test
- build
default:
cache:
key:
files:
- package-lock.json
paths:
- .npm/
- node_modules/
policy: pull-push
test:
stage: test
image: node:20
script:
- npm ci --cache .npm --prefer-offline
- npm test
artifacts:
when: always
expire_in: 3 days
paths:
- reports/
build:
stage: build
image: node:20
script:
- npm ci --cache .npm --prefer-offline
- npm run build
- tar -czf dist/app.tar.gz dist/
artifacts:
expire_in: 7 days
paths:
- dist/app.tar.gz
- cache привязан к lockfile, поэтому не раздувается «по коммитам»;
- artifacts с отчётами живут 3 дня, а билд — 7 дней;
- в artifacts не попадают зависимости и мусор сборки.
Типовые ошибки и быстрые симптомы
Кэш «не работает»: каждый раз качает заново
Частая причина — слишком уникальный cache:key. Симптом: в логах job’ы постоянный cache miss и создание нового архива кэша.
Кэш «ломает сборку»: странные падения после обновлений
Причина — слишком общий ключ и отсутствие «инвалидации» при смене зависимостей или версии runtime. Решение: ключ по lockfile и добавление версии инструмента/образа в стратегию ключа.
GitLab раздувается: артефакты висят месяцами
Причина — нет expire_in или он одинаково длинный для всех job’ов. Введите классы артефактов: короткие для тестов, средние для сборок, длинные — только для релизных тегов.
Итог
Управление GitLab CI storage — это баланс между скоростью и дисциплиной хранения. Cache отвечает за ускорение и может быть удалён почти без риска, но требует разумного cache:key и регулярной чистки runner cache. Artifacts — это результаты и доказательства: задавайте expire_in, оставляйте только нужные файлы и не превращайте artifacts в перенос «всего проекта» между стадиями.
Если один раз разложить по полочкам «что кэшируем», «что сохраняем как артефакты» и «как чистим», то пайплайны будут быстрыми и предсказуемыми, а диски перестанут заканчиваться в самый неподходящий момент.


