Trivy давно стал «дефолтным» инструментом для container security у тех, кто хочет получать быстрые и воспроизводимые результаты прямо в CI/CD. Он объединяет сканирование уязвимостей, поиск секретов, анализ конфигураций и генерацию SBOM. В этой статье соберём рабочий пайплайн: где и как запускать Trivy, как настроить fail-threshold по уровню severity, как управлять шумом, генерировать SBOM и хранить отчёты, а также как не растратить минуты CI на загрузку базы.
Зачем Trivy в CI/CD и где его запускать
Сканирование «на локалке» хорошо для первичной оценки, но безопасность выигрывает, когда проверки запускаются неизбежно, одинаково и на каждом коммите/релизе. CI/CD — идеальное место: вы закрепляете политику, не полагаетесь на ручные проверки и можете блокировать релизы при нарушении порогов риска. Trivy отлично подходит для этого: бинарь лёгкий, не требует сложной инсталляции, работает как с файловой системой, так и с образами в реестре, а также умеет собирать SBOM в популярных форматах.
Точки встраивания:
- Перед сборкой: анализ Dockerfile и зависимостей репозитория (
trivy fs,trivy config). - После сборки: скан Docker-образа (
trivy image) до публикации в реестр. - После публикации: периодический скан по тегу/дижесту в реестре для выявления «поздних» уязвимостей.
 
Какие проверки включать
Trivy поддерживает несколько сканеров: vuln (уязвимости), secret (секреты в коде), config (мисконфиги IaC и Kubernetes-манифестов), license (лицензии пакетов). В CI/CD обычно начинают с уязвимостей образа и постепенно добавляют остальные, закрепляя политику «что блокирует релиз, а что — только предупреждение».
Для самостоятельных раннеров и приватного реестра удобно использовать изолированную инфраструктуру на облачном VDS.
Базовый скан Docker-образа и fail-threshold по severity
Самый простой и при этом практичный вариант: сканировать собранный образ и «проваливать» джоб, если обнаружены уязвимости требуемой важности. В Trivy это реализуется комбинацией фильтра по --severity и кода выхода --exit-code. По сути, это и есть ваш fail-threshold: все найденные уязвимости с уровнем не ниже выбранного порога приведут к ненулевому exit code и остановят пайплайн.
trivy image --severity HIGH,CRITICAL --exit-code 1 --ignore-unfixed --no-progress myrepo/app:build-123
Пояснения:
--severity HIGH,CRITICAL— фильтрует вывод и порог риска; всё равно ниже порога можно посмотреть в отдельном отчёте.--exit-code 1— обрушит джоб, если найдено хотя бы одно совпадение под выбранные severity.--ignore-unfixed— исключит нефиксированные уязвимости дистрибутива (уменьшает шум, полезно для базовых образов).--no-progress— чистый лог для CI.
Если вам важен более мягкий порог (например, «не падать при HIGH, падать только при CRITICAL»), меняйте --severity или делайте два шага: один с порогом HIGH на предупреждение (exit code 0), второй — строгий с CRITICAL и --exit-code 1.
Актуальность базы уязвимостей и экономия минут CI
Первый запуск Trivy скачивает базу уязвимостей. На «холодных» раннерах это может занимать ощутимое время. В CI важно:
- Кэшировать директорию Trivy: 
~/.cache/trivyлибо путь, указанный вTRIVY_CACHE_DIR. - Явно вызывать предзагрузку базы отдельным шагом, чтобы разделить сетевую часть и скан.
 - Периодически обновлять базу, но не на каждом шаге, если у вас плотные пайплайны.
 
# Предзагрузка базы (опционально)
trivy image --download-db-only
# Последующие сканы без повторной загрузки
trivy image --skip-db-update --severity HIGH,CRITICAL --exit-code 1 myrepo/app:build-123
На некоторых раннерах выгодно хранить кэш между джобами/ветками. Не забудьте инвалидировать кэш при редких, но важных изменениях (например, раз в сутки).

Снижаем шум: .trivyignore и селективные исключения
Любой реальный проект сталкивается с «шумом»: уязвимости в инструментах, которые вы не используете, временно нефиксированные проблемы в базовом образе и т.п. Trivy предлагает два механизма:
- Простой файл 
.trivyignore— перечисление ID уязвимостей (CVE и аналоги), которые нужно игнорировать. .trivyignore.yaml— расширенный формат с условиями, комментариями и сроком действия исключения.
# .trivyignore (простой формат)
CVE-2023-12345
CVE-2022-99999
# .trivyignore.yaml (пример со сроком и обоснованием)
ignoreRules:
  - id: CVE-2023-12345
    reason: В компоненте неиспользуемая функциональность; сменим базовый образ в Q2
    expiresOn: 2025-06-30
  - id: CVE-2022-99999
    reason: false positive для версии libxyz в alpine:3.19
Подход с истекающими исключениями дисциплинирует: исключение не «вечное», и CI напомнит, когда пора его пересмотреть. Важно хранить эти файлы в репозитории рядом с кодом — это и есть «политика как код».
SBOM: зачем, как генерировать и хранить
SBOM (Software Bill of Materials) — перечень компонентов вашего образа с версиями и метаданными. SBOM помогает выполнять требования комплаенса, ускоряет расследование инцидентов («а у нас есть уязвимый пакет X?») и позволяет сравнивать состав между релизами. Trivy умеет собирать SBOM в популярных форматах CycloneDX и SPDX.
# Генерация SBOM для образа в формате CycloneDX JSON
trivy sbom --format cyclonedx --output sbom.cdx.json myrepo/app:build-123
# В формате SPDX JSON
trivy sbom --format spdx-json --output sbom.spdx.json myrepo/app:build-123
Практика: сохраняйте SBOM как артефакт сборки и как метаданные релиза; при необходимости отправляйте в реестр артефактов. Если в вашей организации принят единый формат (чаще CycloneDX JSON), придерживайтесь его и автоматизируйте проверку валидности.
Форматы отчётов и интеграция с системами обзора
Trivy умеет выводить результат не только в таблицу, но и в машинные форматы: JSON для последующей агрегации, SARIF для систем кода-ревью. Так вы получаете статус «прошёл/не прошёл» и одновременно полезный артефакт для аналитики.
# JSON-отчёт (для артефактов/агрегации)
trivy image --severity HIGH,CRITICAL --format json --output trivy-vuln.json myrepo/app:build-123
# SARIF (для аннотаций в обзоре кода)
trivy image --severity HIGH,CRITICAL --format sarif --output trivy.sarif myrepo/app:build-123
Хороший приём — не смешивать «блокирующий» шаг и генерацию расширенных отчётов. Сначала быстрый фильтр с --severity и --exit-code, затем — дополнительный шаг, который всегда выполняется и выгружает подробную аналитику (даже если релиз заблокирован).

Политика как код: от правил к автоматике
Базового порога --severity часто достаточно, но зрелые команды идут дальше: по проектам задаются разные пороги и исключения, часть проверок идёт как «warning», а часть — как «error». Это удобно описывать как код: конфиг-профили сканирования, .trivyignore(.yaml), преднастроенные форматы отчётов, репозиторные правила. Кроме того, Trivy поддерживает Rego-политики для выборочных игноров находок и расширенного контроля, используя ключи, доступные в вашей версии инструмента (например, загрузка Policy Bundle).
Совет: начните с простого профиля «CRITICAL блокирует релиз, HIGH помечаются как предупреждения». Через 2–4 недели пересмотрите долю «ложного шума» и закройте его точечными исключениями с истечением срока. Потом постепенно ужесточайте пороги.
GitHub Actions: минимальная рабочая конфигурация
Ниже пример, где первый шаг прогревает базу, второй — выполняет «строгий» скан и падает при HIGH/CRITICAL, а третий сохраняет SBOM и JSON-отчёт артефактами:
name: security-scan
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
jobs:
  trivy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Docker build
        run: docker build -t myrepo/app:${{ github.sha }} .
      - name: Trivy DB warmup
        run: trivy image --download-db-only
      - name: Trivy image scan (fail on HIGH+)
        run: trivy image --severity HIGH,CRITICAL --exit-code 1 --ignore-unfixed --no-progress myrepo/app:${{ github.sha }}
      - name: Trivy SBOM and reports
        if: always()
        run: |
          trivy sbom --format cyclonedx --output sbom.cdx.json myrepo/app:${{ github.sha }}
          trivy image --format json --output trivy-vuln.json myrepo/app:${{ github.sha }}
      - name: Upload artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: trivy-reports
          path: |
            trivy-vuln.json
            sbom.cdx.json
GitLab CI: кеш и строгий порог
В GitLab удобно вынести кэш Trivy в общий ключ и разделить шаги:
stages:
  - build
  - scan
cache:
  key: trivy-cache
  paths:
    - .trivy-cache/
variables:
  TRIVY_CACHE_DIR: .trivy-cache
build:
  stage: build
  script:
    - docker build -t registry.example.com/app:$CI_COMMIT_SHA .
trivy:db:
  stage: scan
  script:
    - trivy image --download-db-only
  artifacts:
    expire_in: 1 day
    paths:
      - .trivy-cache/
trivy:scan:
  stage: scan
  dependencies: ["trivy:db"]
  script:
    - trivy image --skip-db-update --severity HIGH,CRITICAL --exit-code 1 --ignore-unfixed registry.example.com/app:$CI_COMMIT_SHA
  artifacts:
    when: always
    paths:
      - trivy-vuln.json
      - sbom.cdx.json
  after_script:
    - trivy image --skip-db-update --format json --output trivy-vuln.json registry.example.com/app:$CI_COMMIT_SHA
    - trivy sbom --format cyclonedx --output sbom.cdx.json registry.example.com/app:$CI_COMMIT_SHA
Jenkins Pipeline: быстрый ввод
Jenkins традиционно даёт большую гибкость. Пример Jenkinsfile с отдельными стадиями и строгим порогом:
pipeline {
  agent any
  stages {
    stage('Build') {
      steps {
        sh 'docker build -t myrepo/app:${BUILD_NUMBER} .'
      }
    }
    stage('Trivy DB warmup') {
      steps {
        sh 'trivy image --download-db-only'
      }
    }
    stage('Scan (fail on HIGH+)') {
      steps {
        sh 'trivy image --severity HIGH,CRITICAL --exit-code 1 --ignore-unfixed myrepo/app:${BUILD_NUMBER}'
      }
    }
    stage('Reports') {
      when { expression { true } }
      steps {
        sh 'trivy image --format json --output trivy-vuln.json myrepo/app:${BUILD_NUMBER}'
        sh 'trivy sbom --format cyclonedx --output sbom.cdx.json myrepo/app:${BUILD_NUMBER}'
        archiveArtifacts artifacts: 'trivy-vuln.json, sbom.cdx.json', fingerprint: true
      }
    }
  }
}
Сканирование до сборки: Dockerfile, репозиторий, конфигурации
Критичные находки лучше ловить раньше, чем вы потратите минуты CI на сборку. Добавьте лёгкие проверки до сборки образа:
trivy fs .— обнаружение известных уязвимых зависимостей и секретов в репозитории.trivy config .— анализ конфигураций IaC и манифестов на мисконфиги.- Быстрый линт Dockerfile: базовый образ, слой с установкой пакетов, удаление кэшей менеджеров пакетов, пользователь по умолчанию.
 
Эти шаги обычно не «роняют» пайплайн на первых итерациях — пусть отчёты копятся, а команда исправляет постепенно. После стабилизации можно включать более строгие пороги.
Скан удалённого образа в реестре и «поздние» уязвимости
Уязвимости появляются и после релиза: базы обновляются ежедневно. Полезно добавить периодические джобы, которые сканируют образы по тегам/дижестам прямо в реестре. Пример:
trivy image --severity HIGH,CRITICAL --exit-code 1 --ignore-unfixed registry.example.com/app:1.4.2
Так вы поймаете «поздние» критики и запустите процедуру внепланового обновления. Для приватного реестра удобно настроить автоматический TLS и реверс‑прокси на входе.
Практические советы по Dockerfile для снижения риска
Trivy лишь выявляет проблемы. Чтобы их было меньше, придерживайтесь нескольких правил при сборке образов:
- Выбирайте минималистичные базовые образы и своевременно обновляйте теги (не застревайте на «latest» без контроля).
 - Устанавливайте пакеты точечно и удаляйте кэш менеджера пакетов в том же слое, чтобы не тащить лишнее в финальный образ.
 - Следите за апстримом: обновление базового образа нередко «выносит» десятки уязвимостей без единой строки вашего кода.
 - Разделяйте сборку и рантайм через multi-stage build; финальный образ должен содержать только необходимое.
 - Запускайте процесс под непривилегированным пользователем; многие сканеры (и аудиторы) отмечают это как критичную практику безопасности.
 
Расширения: секреты, лицензии и комбинированные профили
Помимо уязвимостей, постепенно включайте другие сканеры:
- Секреты (
--scanners secret): быстрый способ отловить случайно закоммиченные токены/ключи. - Лицензии (
--scanners license): контроль нежелательных OSS-лицензий в зависимостях. - Конфигурации (
trivy config): проверка IaC/Kubernetes на мисконфиги. 
Комбинируйте проверки по приоритету: секреты и критичные мисконфиги чаще всего заслуживают блокирующего порога, лицензии — предупреждения и отчёт.
Диагностика и устойчивость пайплайна
Иногда скан «шевелит» нестабильные сети или артефакт-реестры. Несколько флагов повышают устойчивость:
--timeout— увеличить таймаут сетевых операций.--retryи--retry-max-backoff(если доступны в вашей версии) — на случай непредсказуемых сбоев.--quiet— для чистых логов без прогресса и лишних сообщений.
Отдельно контролируйте размер логов: текстовые таблицы удобны глазу, но для артефактов лучше JSON или SARIF. Включите ротацию артефактов в CI и храните долгосрочно только то, что нужно для аудита. Для централизации логов сборки и инфраструктуры пригодится материал про логирование в JSON и отправку в Loki/ELK.
Как подойти к «идеальному» fail-threshold
Единого универсального порога не существует. На практике хорошо работает поэтапное внедрение:
- Неделя 1–2: блокируем только 
CRITICAL, собираем отчёты поHIGHв артефакты. - Неделя 3–4: закрываем «быстрые» HIGH, документируем исключения с истечением срока.
 - Далее: поднимаем порог до «падать на HIGH+», оставляя редкие исключения под контроль.
 
При таком подходе команда не тонет в «красных» билдах, но движется к реальной безопасности, а не к «галочке» в чек-листе.
Частые ошибки и как их избежать
- Скан только локально. Решение — автоматизировать в CI/CD, назначить пороги и артефакты отчётов.
 - Отсутствие кэша базы. Решение — кэшировать 
TRIVY_CACHE_DIRи разделять обновление БД и скан. - Слишком жёсткий порог с первого дня. Итог — постоянные «красные» сборки. Решение — staged rollout порогов.
 - Вечные исключения. Решение — использовать 
.trivyignore.yamlсexpiresOnи регулярным пересмотром. - Скан только до публикации. Решение — периодический пост-релизный скан по дижесту.
 
Итоги
Trivy закрывает 80% задач container security в CI/CD с минимальной ценой внедрения. Комбинация фильтра по severity, строгого --exit-code как fail-threshold, дисциплины исключений и регулярной генерации SBOM превращает «сканер по требованию» в надёжный контрольный пункт конвейера. Начните с простого: прогрейте базу, сканируйте собранный образ, блокируйте только CRITICAL и складывайте отчёты в артефакты. Через месяц у вас будет достаточно данных, чтобы принять зрелую политику для HIGH и добавлять скан мисконфигов, секретов и лицензий. Безопасность — это не разовый акт, а привычка, и Trivy встраивает её прямо в ваш процесс.
                            

