TechFeedTechFeed
Security

컨테이너 이미지 보안 스캐닝 실전 가이드 — Trivy, Grype, CI/CD 통합

Trivy 설치·사용법, Grype/Snyk 비교, GitHub Actions CI/CD 통합, Dockerfile 보안 베스트 프랙티스, 취약점 우선순위 판단.

한 줄 요약: 컨테이너 이미지 보안 스캐닝은 배포 전 알려진 CVE와 잘못된 설정을 자동으로 탐지하는 필수 DevSecOps 단계다. Trivy 한 줄 명령으로 시작할 수 있다.

이 글이 필요한 사람:

  • Docker 이미지를 프로덕션에 배포하는 백엔드/DevOps 엔지니어
  • CI/CD 파이프라인에 보안 검사를 추가하려는 팀
  • 컨테이너 보안 도구를 비교하고 선택해야 하는 시큐리티 담당자
  • Trivy, Grype, Snyk 중 어떤 도구를 쓸지 결정하지 못한 개발자

왜 컨테이너 이미지 보안 스캐닝이 필요한가

컨테이너 이미지는 OS 라이브러리, 런타임, 애플리케이션 의존성을 한 레이어에 묶는다. node:18-alpine 같은 공식 베이스 이미지도 알려진 CVE를 포함할 수 있다. 2023년 Sysdig 보고서에 따르면 퍼블릭 컨테이너 이미지의 87%가 고위험 이상 취약점을 포함하고 있었다.

문제는 빌드 시점이 아니라 운영 시점에 취약점이 알려진다는 점이다. 6개월 전에 빌드한 이미지가 오늘 기준 CRITICAL 취약점을 가질 수 있다. 스캐닝 없이 배포하면 공격 표면을 그대로 열어두는 것과 같다.

  • 공급망 공격 방어: 베이스 이미지나 패키지 레지스트리가 악성 코드를 포함할 경우 조기 탐지
  • 규정 준수: SOC 2, ISO 27001, PCI-DSS 등에서 취약점 관리 프로세스를 요구
  • 비용 절감: 운영 중 패치보다 빌드 파이프라인에서 차단하는 것이 훨씬 저렴

Trivy 설치 및 기본 사용법

Trivy는 Aqua Security가 개발한 오픈소스 취약점 스캐너다. 컨테이너 이미지, 파일시스템, Git 저장소, IaC(Terraform, Helm) 등을 스캔할 수 있다. 설치가 단순하고 별도 데몬이 필요 없어 CI/CD 통합이 쉽다.

Trivy 설치 (macOS / Linux / Docker)
# macOS (Homebrew) brew install trivy # Linux (apt) sudo apt-get install wget apt-transport-https gnupg lsb-release wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add - echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main | sudo tee -a /etc/apt/sources.list.d/trivy.list sudo apt-get update sudo apt-get install trivy # Docker로 실행 (설치 없이) docker run --rm aquasec/trivy image nginx:latest

설치 후 이미지를 스캔하는 기본 명령은 다음과 같다. --severity로 출력할 취약점 등급을 필터링하고, --exit-code 1을 지정하면 CRITICAL 취약점 발견 시 CI를 실패시킬 수 있다.

Trivy 기본 스캔 명령
# 기본 스캔 trivy image nginx:latest # 심각도 필터 (CRITICAL, HIGH만) trivy image --severity CRITICAL,HIGH nginx:latest # JSON 출력 (파이프라인 연동용) trivy image --format json --output result.json nginx:latest # CI에서 CRITICAL 발견 시 exit 1 반환 trivy image --exit-code 1 --severity CRITICAL nginx:latest # .trivyignore로 특정 CVE 무시 echo "CVE-2023-XXXXX" >> .trivyignore trivy image --ignorefile .trivyignore nginx:latest # 로컬 빌드 이미지 스캔 docker build -t myapp:latest . trivy image myapp:latest
VEX (Vulnerability Exploitability eXchange) 지원: Trivy 0.46+에서 VEX 문서를 지원한다. 공급업체가 "이 취약점은 우리 제품에서 실제로 악용 불가"라고 명시한 경우 스캔 결과에서 제외할 수 있어 오탐 노이즈를 줄일 수 있다.

Grype / Snyk 등 대안 도구 비교

Trivy만이 선택지가 아니다. 팀 규모, 예산, 스택에 따라 다른 도구가 더 적합할 수 있다.

Grype는 Anchore가 만든 경량 스캐너로, Syft로 SBOM을 먼저 생성한 다음 Grype로 취약점을 매핑하는 패턴을 권장한다. SBOM을 아티팩트로 저장해두면 나중에 새로운 CVE가 공개됐을 때 이미지를 다시 빌드하지 않고도 기존 SBOM에 재스캔이 가능하다.

Grype 사용 예시 (Syft SBOM 조합)
# Grype 설치 curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin # 직접 이미지 스캔 grype nginx:latest # Syft로 SBOM 생성 후 Grype 스캔 syft nginx:latest -o spdx-json > sbom.spdx.json grype sbom:./sbom.spdx.json

CI/CD 파이프라인 통합 — GitHub Actions 예시

보안 스캐닝을 CI 파이프라인에 넣으면 취약 이미지가 레지스트리에 푸시되기 전에 차단된다. 아래는 docker build → trivy scan → push 순서로 구성한 GitHub Actions 워크플로우다.

.github/workflows/docker-security.yml
name: Docker Security Scan on: push: branches: [main] pull_request: branches: [main] jobs: build-and-scan: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Build 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 severity: CRITICAL,HIGH exit-code: '1' - name: Upload SARIF to GitHub Security tab uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: trivy-results.sarif - name: Push to registry (only if scan passed) if: success() run: | docker tag myapp:${{ github.sha }} ghcr.io/${{ github.repository }}:latest docker push ghcr.io/${{ github.repository }}:latest

format: sarif로 출력하면 GitHub Security 탭에서 취약점을 코드 리뷰 형태로 확인할 수 있다. SARIF 업로드는 if: always()로 설정해 스캔 실패 시에도 결과를 확인할 수 있게 한다.

정책 분리 팁: PR 단계에서는 exit-code: 0으로 경고만 하고, main 브랜치 머지 시에는 exit-code: 1로 차단하는 정책을 쓰면 개발 속도와 보안을 균형 있게 유지할 수 있다.

Dockerfile 베스트 프랙티스 — 취약점 최소화

스캐닝은 사후 탐지다. 이미지를 처음부터 작게, 권한 없이 만드는 것이 선제 방어다. 핵심 패턴 두 가지를 짚는다.

1. 멀티스테이지 빌드로 최종 이미지 크기 줄이기

빌드 도구(컴파일러, devDependencies)는 최종 이미지에 포함할 필요가 없다. 멀티스테이지 빌드로 런타임에 필요한 파일만 복사하면 공격 표면이 줄어든다.

멀티스테이지 빌드 예시 (Node.js)
# 빌드 스테이지 FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build # 런타임 스테이지 — 빌드 도구 제외 FROM node:20-alpine AS runtime WORKDIR /app # 비root 사용자 생성 및 적용 RUN addgroup -S appgroup && adduser -S appuser -G appgroup COPY --from=builder --chown=appuser:appgroup /app/dist ./dist COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules USER appuser EXPOSE 3000 CMD ["node", "dist/index.js"]

비root 실행의 중요성: 컨테이너가 root로 실행되면 컨테이너 탈출(container escape) 취약점 악용 시 호스트 시스템 전체가 위험에 노출된다. USER 인스트럭션으로 반드시 비root 사용자로 전환하라.

2. 베이스 이미지 선택 기준

  • alpine 계열: 패키지 수가 적어 취약점이 적다. 단, glibc 대신 musl을 사용하므로 일부 바이너리와 호환 문제가 있을 수 있다.
  • distroless: Google이 제공하는 OS 패키지 없는 이미지. 쉘 자체가 없어 공격자가 exec로 진입할 수 없다. Java, Python, Node 등 지원.
  • Chainguard Images: 서명된 이미지, 최소 패키지, 매일 리빌드. CVE 0건을 목표로 운영된다.

취약점 우선순위 판단 기준

Trivy가 보고하는 수십 개 취약점을 모두 즉시 패치할 수는 없다. 실무에서 쓸 수 있는 우선순위 판단 기준을 정리한다.

취약점 관리는 한 번에 끝나지 않는다. 스캐닝을 파이프라인에 고정하고, 주기적으로 이미지를 리빌드(베이스 이미지 업데이트)하며, .trivyignore에 수용 근거를 주석으로 남기는 것이 지속 운영의 기본이다.

TrivyGrypeDocker컨테이너 보안CI/CD취약점 스캐닝

관련 포스트

OWASP Top 10 2026 — 웹 보안 필수 체크리스트2026-02-18인증 구현 가이드 2026 — JWT, OAuth, Passkey2026-02-20API 보안 체크리스트 20262026-03-06JWT vs 세션 인증 — 무엇을 선택할 것인가2026-03-07