trivyでDockerイメージの脆弱性をスキャンする方法|CI組み込みとベースイメージ更新の運用

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Docker > trivyでDockerイメージの脆弱性をスキャンする方法|CI組み込みとベースイメージ更新の運用
「Dockerイメージをpullしてコンテナを動かしていたら、セキュリティ監査で「このベースイメージに重大な脆弱性がある」と指摘されてしまった」
そんな経験をしたことはないでしょうか。コンテナ技術が現場に普及した今、イメージに含まれるOS・ライブラリの脆弱性を継続的に把握することは、Linuxサーバー管理と同様に不可欠なスキルになっています。

この記事では、OSSの脆弱性スキャナー trivy(トリヴィ)を使って、DockerイメージのCVE(脆弱性)をスキャンする方法を解説します。インストールから基本スキャン、CI/CD(GitHub Actions)への組み込み、ベースイメージ更新の運用設計まで一通り説明します。動作確認環境:Rocky Linux 9.4 / Ubuntu 24.04 LTS。

この記事のポイント

・trivy image <イメージ名> でDockerイメージの脆弱性を数十秒でスキャンできる
・--severity CRITICAL,HIGH フィルタで優先対応すべきCVEだけ絞り込める
・GitHub Actions の aquasecurity/trivy-action でCI段階に自動スキャンを組み込める
・ベースイメージをalpine/distrolessに変え、定期cronスキャンで継続管理が基本戦略


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

trivyとは何か・なぜDockerセキュリティに必要か

trivyはAqua Security社が開発・OSSで公開している脆弱性スキャナーです。Dockerイメージ、OS、ファイルシステム、Gitリポジトリ、Kubernetesクラスタなど幅広い対象をスキャンできますが、現場での主な使い途はDockerイメージのCVEスキャンです。

なぜDockerイメージのスキャンが必要か

コンテナはホストOSから隔離されていますが、イメージに含まれるOS・ライブラリの脆弱性はそのままコンテナ内部に持ち込まれます。「コンテナだから安全」という認識は誤りで、公式イメージ(例: ubuntu:22.04)でも数十件のCVEが含まれていることは珍しくありません。

trivyがスキャンできる主な対象は以下のとおりです。

・コンテナイメージ(Docker Hub・Amazon ECR・Google GCR等)
・ローカルのDockerfile・docker-compose.yml
・OSパッケージ(RPM・deb)
・言語パッケージ(npm・pip・gem・go.sum等)
・インフラコード(Terraform・Kubernetes YAML: Misconfig検出)

脆弱性データベースはNVD(米国立標準技術研究所)と各ディストロのセキュリティアドバイザリを組み合わせており、既知CVEの検出率が高い点が採用理由として挙げられます。Snyk・Clairといった競合ツールもありますが、trivyはインストールが簡単でCI/CD連携が豊富なため、現場での採用率が高いスキャナーです。

trivyのインストールと初回スキャン実行

1. バイナリをダウンロードしてインストールする(Linux)

公式が提供するインストールスクリプトが最もシンプルです。

# インストールスクリプトを実行する(rootまたはsudo) $ curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin # バージョン確認 $ trivy --version Version: 0.52.2 Vulnerability DB: Version: 2 UpdatedAt: 2024-07-01 06:18:11 +0000 UTC NextUpdate: 2024-07-01 12:18:11 +0000 UTC DownloadedAt: 2024-07-01 10:23:45 +0000 UTC

RPM系(RHEL/Rocky Linux)でパッケージマネージャを使う場合はリポジトリを追加します。

# /etc/yum.repos.d/trivy.repo を作成する $ sudo tee /etc/yum.repos.d/trivy.repo << 'REPOEOF' [trivy] name=Trivy repository baseurl=https://aquasecurity.github.io/trivy-repo/rpm/releases/$releasever/$basearch/ gpgcheck=1 enabled=1 gpgkey=https://aquasecurity.github.io/trivy-repo/rpm/public.key REPOEOF # パッケージをインストールする $ sudo dnf install -y trivy

2. 初回スキャン実行(脆弱性DBのダウンロード)

trivyは初回実行時に脆弱性データベース(約200MB)をローカルにダウンロードします。数分かかるため、CI環境ではDBキャッシュを設定しておくと速度が改善します。

# Ubuntuの公式イメージをスキャンする(初回は数分かかる) $ trivy image ubuntu:22.04 2024-07-01T10:23:45+09:00 INFO Vulnerability scanning is enabled 2024-07-01T10:23:45+09:00 INFO Downloading vulnerability DB... 2024-07-01T10:23:48+09:00 INFO [ubuntu] Detecting OS packages... ubuntu:22.04 (ubuntu 22.04) Total: 38 (UNKNOWN: 0, LOW: 16, MEDIUM: 22, HIGH: 0, CRITICAL: 0)

「Total: 38」のように、検出された脆弱性の件数とSeverity(深刻度)別の内訳が表示されます。

Dockerイメージを対象にした脆弱性スキャンの基本操作

1. ローカルビルドイメージをスキャンする

# Dockerfileからイメージをビルドする $ docker build -t myapp:latest . # ローカルイメージをスキャンする $ trivy image myapp:latest

docker daemon上にあるイメージはイメージ名をそのまま指定すれば自動で認識されます。Docker Hubから未pullのイメージも、名前を指定するだけで自動的にpullしてスキャンします。

2. SeverityフィルタでCRITICAL/HIGHのみ表示する

件数が多い場合は --severity でフィルタリングします。実務ではまずCRITICAL/HIGHに絞って対応を始めるのが現実的です。

# CRITICALとHIGHのみ表示する $ trivy image --severity CRITICAL,HIGH myapp:latest myapp:latest (debian 12.5) Total: 5 (HIGH: 4, CRITICAL: 1) Library Vulnerability Severity Installed Version Fixed Version --------------- --------------- -------- ----------------------- --------------- openssl CVE-2023-0286 CRITICAL 3.0.2-0ubuntu1.12 3.0.2-0ubuntu2 libssl3 CVE-2024-0727 HIGH 3.0.2-0ubuntu1.12 3.0.2-0ubuntu3 curl CVE-2023-38545 HIGH 7.81.0-1ubuntu1.13 7.81.0-1ubuntu2 libc6 CVE-2023-4911 HIGH 2.35-0ubuntu3.4 2.35-0ubuntu3.5 libsystemd0 CVE-2023-26604 HIGH 249.11-0ubuntu3.11 249.11-0ubuntu3.12

3. JSON出力でCIに組み込みやすくする

# JSON形式でファイルに出力する $ trivy image --format json --output trivy-report.json myapp:latest # 内容確認(python3で整形表示) $ python3 -m json.tool trivy-report.json | head -20 { "SchemaVersion": 2, "ArtifactName": "myapp:latest", "ArtifactType": "container_image", "Metadata": { "OS": { "Family": "debian", "Name": "12.5" } }, "Results": [...] }

JSON出力はSlack通知・チケット起票の自動化など、後段の処理と連携しやすい形式です。

スキャン結果の見方・CVSSスコアと優先対応方針

trivyのSeverityはNVDのCVSS(Common Vulnerability Scoring System)スコアに基づいています。

Severity CVSSスコア 対応方針の目安
CRITICAL 9.0~10.0 即時対応必須。本番リリースをブロックする
HIGH 7.0~8.9 次スプリント内に修正。放置厳禁
MEDIUM 4.0~6.9 四半期内に対応計画を立てる
LOW 0.1~3.9 バックログに積む。記録だけは必須
UNKNOWN 評価なし ディストロ未評価。NVDで個別確認する
実務での優先対応フローは次のとおりです。

まずスキャン結果の「Fixed Version」列を確認します。修正バージョンが存在する場合は、パッケージアップデートまたはベースイメージの更新で対処できます。一方、「Fixed Version」が空欄(まだ修正なし)の場合は、ワークアラウンドの検討(対象機能の無効化・ネットワーク分離等)が必要です。

--ignore-unfixed オプションを付けると修正バージョンが存在するCVEのみを表示します。対応できないノイズを排除して、本当に今すぐ直せる脆弱性に集中できます。

# 修正済みバージョンが存在するCVEのみ表示する $ trivy image --severity CRITICAL,HIGH --ignore-unfixed myapp:latest myapp:latest (debian 12.5) Total: 3 (HIGH: 2, CRITICAL: 1) # 修正不可のCVEが除外され、今すぐ対応できる件数のみ表示される

CI/CDパイプライン(GitHub Actions)へのtrivy組み込み

Aqua Securityは公式のGitHub Actionsアクション aquasecurity/trivy-action を提供しています。これをワークフローに追加するだけで、PRやマージのたびにスキャンが自動実行されます。

# .github/workflows/trivy-scan.yml name: Trivy Vulnerability Scan on: push: branches: [ "main" ] pull_request: branches: [ "main" ] schedule: # 毎週月曜日 02:00 UTC に定期実行 - cron: '0 2 * * 1' jobs: scan: name: Build and Scan runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Build Docker image run: docker build -t myapp:${{ github.sha }} . - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master with: image-ref: 'myapp:${{ github.sha }}' format: 'sarif' output: 'trivy-results.sarif' exit-code: '1' ignore-unfixed: true severity: 'CRITICAL,HIGH' - name: Upload results to GitHub Security tab uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: 'trivy-results.sarif'

設定のポイントをまとめます。

exit-code: '1'を設定するとCRITICAL/HIGH検出時にCIがfailし、問題のあるイメージのデプロイを自動でブロックできる
ignore-unfixed: trueで対応不能なCVEを除外し、ノイズを減らして本当に直せる問題だけを検出する
scheduleでコード変更とは独立した定期スキャンを設定すると、コード変更なしにDBが更新された新規CVEも検出できる
・SARIF形式で出力するとGitHub Security タブに結果が表示され、チーム全体で把握しやすくなる

なお、CIでDockerイメージが公開するポートについても、コンテナ起動後にLinux ポート確認の全コマンドを使って意図しないポートが開いていないかを確認する習慣をつけることをお勧めします。

ベースイメージの更新戦略と定期スキャン自動化

1. ベースイメージ選択で脆弱性数を大幅に削減する

CVE件数を最も効果的に減らす方法は、ベースイメージそのものの見直しです。フルOSイメージからalpineやdistrolessに変更するだけで、CRITICAL/HIGH件数がゼロになるケースも多いです。

# ベースイメージ別の脆弱性件数を比較する(--severity CRITICAL,HIGH) $ trivy image ubuntu:22.04 --severity CRITICAL,HIGH 2>&1 | tail -3 ubuntu:22.04 (ubuntu 22.04) Total: 18 (HIGH: 18, CRITICAL: 0) $ trivy image python:3.12-slim-bookworm --severity CRITICAL,HIGH 2>&1 | tail -3 python:3.12-slim-bookworm (debian 12.5) Total: 3 (HIGH: 3, CRITICAL: 0) $ trivy image python:3.12-alpine --severity CRITICAL,HIGH 2>&1 | tail -3 python:3.12-alpine3.19 (alpine 3.19.1) Total: 0 (HIGH: 0, CRITICAL: 0) $ trivy image gcr.io/distroless/python3-debian12 --severity CRITICAL,HIGH 2>&1 | tail -3 gcr.io/distroless/python3-debian12 (debian 12.5) Total: 0 (HIGH: 0, CRITICAL: 0)

alpine系はパッケージが少ないため攻撃面(アタックサーフェス)が小さく、distrolessはシェル・パッケージマネージャ自体が含まれないため、さらに高いセキュリティ水準を実現できます。

2. マルチステージビルドで実行イメージを軽量化する

ビルドツールを含むイメージをそのまま本番で動かすのは避けてください。マルチステージビルドでビルド環境と実行環境を分離し、最終イメージにはalpineやdistrolessを使います。

# Dockerfile(マルチステージビルドの例) # --- ビルドステージ --- FROM golang:1.22 AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o myapp . # --- 実行ステージ(alpine: 軽量・脆弱性が少ない)--- FROM alpine:3.19 RUN apk --no-cache add ca-certificates tzdata WORKDIR /app COPY --from=builder /app/myapp . USER nobody CMD ["./myapp"]

USER nobody でrootless実行にすることも、コンテナセキュリティの基本として合わせて設定しておきましょう。

3. 定期スキャンをcronで自動化する

CI/CDを経ないデプロイ済みイメージも継続的な監視対象です。Linuxサーバー上でcronを設定し、定期的にスキャンして結果をファイルに記録します。

# /usr/local/bin/trivy-daily-scan.sh #!/bin/bash SCAN_DATE=$(date +%Y%m%d) REPORT_DIR=/var/log/trivy IMAGE_LIST="myapp:latest nginx:1.25 redis:7.2" mkdir -p "$REPORT_DIR" for IMAGE in $IMAGE_LIST; do IMAGE_SLUG=$(echo "$IMAGE" | sed 's/[:/]/__/g') REPORT_FILE="${REPORT_DIR}/${SCAN_DATE}_${IMAGE_SLUG}.txt" echo "=== スキャン開始: $IMAGE ===" > "$REPORT_FILE" trivy image --severity CRITICAL,HIGH --ignore-unfixed "$IMAGE" >> "$REPORT_FILE" 2>&1 echo "=== スキャン完了: $(date) ===" >> "$REPORT_FILE" done # CRITICALが検出された場合はメール通知する CRITICAL_COUNT=$(grep -rl "CRITICAL" "${REPORT_DIR}/${SCAN_DATE}"_*.txt 2>/dev/null | wc -l) if [ "$CRITICAL_COUNT" -gt 0 ]; then echo "trivyスキャンでCRITICAL脆弱性がある${CRITICAL_COUNT}件のイメージが検出されました。${REPORT_DIR}を確認してください。" | mail -s "[要対応] Trivy CRITICAL検出 $(date +%Y-%m-%d)" admin@example.com fi

# スクリプトに実行権限を付ける $ sudo chmod 755 /usr/local/bin/trivy-daily-scan.sh # crontabに登録する(毎日午前2時に実行) $ sudo crontab -e # 以下の行を追加する 0 2 * * * /usr/local/bin/trivy-daily-scan.sh

脆弱性DBはtrivyが起動するたびに自動更新されるため、スクリプト側でDB更新コマンドを別途実行する必要はありません。

まとめ・Docker脆弱性管理の継続的な運用へ

trivyを使ったDockerイメージの脆弱性スキャンと、CI/CDへの組み込み・ベースイメージ更新戦略を解説しました。

やりたいこと コマンド/設定
Dockerイメージをスキャンする trivy image <イメージ名>
CRITICAL/HIGHのみ表示する trivy image --severity CRITICAL,HIGH <イメージ名>
修正済みCVEのみ対象にする trivy image --ignore-unfixed <イメージ名>
JSON形式で出力する trivy image --format json --output report.json <イメージ名>
CIスキャンをGitHub Actionsに組み込む aquasecurity/trivy-action を使用する
ベースイメージで脆弱性数を減らす alpine:3.19 / distrolessに変更する
定期スキャンを自動化する cron + trivy-daily-scan.sh を設定する
・trivy単体でのスキャンはすぐに始められるが、CI組み込みと定期cronの両輪で「コード変更時」と「DB更新時」の両方をカバーするのが本格的な運用体制
・ベースイメージの見直し(ubuntu系 → alpine / distroless)が脆弱性件数を最も大きく削減できる一手
--ignore-unfixed で対応できないノイズを排除し、エンジニアの対応コストを現実的な範囲に抑えることが継続運用のコツ

よくあるエラーとトラブルシュート

trivyを運用していると、以下のような問題に遭遇することがあります。事前に把握しておくことをお勧めします。

1. 「FATAL Failed to download vulnerability DB」が出た場合

初回実行時やCI環境でネットワーク制限がある場合に発生します。

# エラー例 FATAL Failed to download vulnerability DB error: OCI error # 対処1: DBキャッシュディレクトリを明示する $ TRIVY_CACHE_DIR=/tmp/trivy-cache trivy image myapp:latest # 対処2: CI環境でDBをキャッシュする(GitHub Actionsの例) - name: Cache Trivy DB uses: actions/cache@v4 with: path: ~/.cache/trivy key: trivy-db-${{ github.run_id }} restore-keys: trivy-db-

2. GitHub ActionsのCIが意図せずfailする場合

exit-code: '1' を設定しているとCRITICAL/HIGH検出でCIが失敗します。既存の脆弱性に対しては「許可リスト」(.trivyignore)で一時的に除外し、新規CVEのみをブロックする運用が現実的です。

# .trivyignore(プロジェクトルートに配置) # 対応不可の既知CVEを一時除外する(理由をコメントで必ず残すこと) CVE-2023-12345 # 2024-09-30まで猶予(ベースイメージ側の修正待ち) CVE-2022-99999 # 当環境では対象機能を使っていないため影響なし # --ignorefile オプションで指定することもできる $ trivy image --ignorefile .trivyignore myapp:latest

【注意】.trivyignoreへの追加は必ずチームレビューを経ること。理由と期限を記録せずに除外するのは危険で、脆弱性の放置につながります。

3. スキャン結果の件数が多すぎて対応できない場合

初めてtrivyを導入すると数百件のCVEが検出されることがあります。焦って全件対応しようとするのではなく、段階的に絞り込むアプローチが現実的です。

ステップ1: --severity CRITICAL のみにして本当に緊急なものを特定する
ステップ2: --ignore-unfixed を追加して「今すぐ直せる」ものに限定する
ステップ3: ベースイメージをalpineに変更して根本的に件数を減らす
ステップ4: 残った件数をバックログに積んで計画的に消化する

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、trivyによる脆弱性管理を含むDockerのDevSecOps運用を基礎から習得できる講座を用意しています。
Dockerマスター講座の詳細はこちら >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。