そんな経験をしたことはないでしょうか。コンテナ技術が現場に普及した今、イメージに含まれる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スキャンで継続管理が基本戦略
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
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
# /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)
Dockerイメージを対象にした脆弱性スキャンの基本操作
1. ローカルビルドイメージをスキャンする
# Dockerfileからイメージをビルドする $ docker build -t myapp:latest . # ローカルイメージをスキャンする $ trivy image myapp:latest
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": [...] }
スキャン結果の見方・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)
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
まとめ・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 を設定する |
・ベースイメージの見直し(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
3. スキャン結果の件数が多すぎて対応できない場合
初めてtrivyを導入すると数百件のCVEが検出されることがあります。焦って全件対応しようとするのではなく、段階的に絞り込むアプローチが現実的です。ステップ1:
--severity CRITICAL のみにして本当に緊急なものを特定するステップ2:
--ignore-unfixed を追加して「今すぐ直せる」ものに限定するステップ3: ベースイメージをalpineに変更して根本的に件数を減らす
ステップ4: 残った件数をバックログに積んで計画的に消化する
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、trivyによる脆弱性管理を含むDockerのDevSecOps運用を基礎から習得できる講座を用意しています。
→ Dockerマスター講座の詳細はこちら >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 前のページへ:Dockerのセキュリティ設計|非rootユーザー実行とrootlessモードでコンテナを堅牢化する方法
- この記事の属するカテゴリ:Dockerへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます