Configure Podman image scanning with Trivy security vulnerability detection

Intermediate 45 min Apr 22, 2026 36 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up automated container image security scanning using Trivy with Podman to detect vulnerabilities, misconfigurations, and security issues before deploying containers to production.

Prerequisites

  • Root or sudo access
  • Internet connectivity for downloading vulnerability databases
  • At least 2GB free disk space
  • Basic knowledge of containers and JSON

What this solves

Container images often contain vulnerabilities in base operating systems, libraries, and dependencies that attackers can exploit. Trivy security scanner integrates with Podman to automatically detect CVEs, secrets, misconfigurations, and license issues in your container images before deployment. This tutorial shows you how to set up automated scanning workflows that catch security problems early in your development pipeline.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you have the latest security updates.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install Podman

Install Podman container engine if not already present on your system.

sudo apt install -y podman
sudo dnf install -y podman

Install Trivy security scanner

Download and install the latest version of Trivy from the official repository.

sudo apt update
sudo apt install -y 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 update
sudo apt install -y trivy
sudo tee /etc/yum.repos.d/trivy.repo << EOF
[trivy]
name=Trivy repository
baseurl=https://aquasecurity.github.io/trivy-repo/rpm/releases/\$basearch/
gpgcheck=1
enabled=1
gpgkey=https://aquasecurity.github.io/trivy-repo/rpm/public.key
EOF
sudo dnf update -y
sudo dnf install -y trivy

Configure Trivy database location

Set up Trivy database directory with proper permissions for vulnerability database updates.

sudo mkdir -p /var/lib/trivy
sudo chown $USER:$USER /var/lib/trivy
export TRIVY_CACHE_DIR=/var/lib/trivy

Update Trivy vulnerability database

Download the latest vulnerability database which contains CVE information for scanning.

trivy image --download-db-only

Configure Podman with Trivy integration

Create scanning configuration file

Set up Trivy configuration file with security scanning policies and output formats.

cache:
  dir: /var/lib/trivy
db:
  skip-update: false
  download-timeout: 10m
vulnerability:
  type:
    - os
    - library
  scanners:
    - vuln
    - secret
    - config
severity:
  - UNKNOWN
  - LOW
  - MEDIUM
  - HIGH
  - CRITICAL
format: table
output: /var/log/trivy-scan.log
ignore-unfixed: false
timeout: 5m

Create log directory with proper permissions

Set up logging directory where Trivy will store scan results and reports.

sudo mkdir -p /var/log/trivy
sudo chown $USER:$USER /var/log/trivy
sudo chmod 755 /var/log/trivy

Create scanning wrapper script

Build an automation script that integrates Trivy scanning with Podman image operations.

#!/bin/bash

Podman + Trivy security scanning wrapper

set -e

Configuration

TRIVY_CONFIG="/etc/trivy/trivy.yaml" LOG_DIR="/var/log/trivy" DATE=$(date +%Y%m%d_%H%M%S)

Function to scan image

scan_image() { local image="$1" local output_file="${LOG_DIR}/scan_${DATE}_$(echo "$image" | tr '/:' '_').json" echo "Scanning image: $image" echo "Output file: $output_file" # Run Trivy scan trivy image \ --config "$TRIVY_CONFIG" \ --format json \ --output "$output_file" \ --severity HIGH,CRITICAL \ "$image" # Check for critical vulnerabilities critical_count=$(jq -r '.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL") | .VulnerabilityID' "$output_file" 2>/dev/null | wc -l) high_count=$(jq -r '.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH") | .VulnerabilityID' "$output_file" 2>/dev/null | wc -l) echo "Critical vulnerabilities: $critical_count" echo "High vulnerabilities: $high_count" # Generate summary report trivy image \ --config "$TRIVY_CONFIG" \ --format table \ --severity HIGH,CRITICAL \ "$image" # Return exit code based on findings if [ "$critical_count" -gt 0 ]; then echo "ERROR: Critical vulnerabilities found in $image" return 1 elif [ "$high_count" -gt 5 ]; then echo "WARNING: More than 5 high-severity vulnerabilities found in $image" return 2 fi echo "Image $image passed security scan" return 0 }

Function to pull and scan

pull_and_scan() { local image="$1" echo "Pulling image: $image" podman pull "$image" echo "Scanning pulled image..." scan_image "$image" }

Main execution

case "$1" in scan) if [ -z "$2" ]; then echo "Usage: $0 scan " exit 1 fi scan_image "$2" ;; pull-scan) if [ -z "$2" ]; then echo "Usage: $0 pull-scan " exit 1 fi pull_and_scan "$2" ;; *) echo "Usage: $0 {scan|pull-scan} " echo " scan - Scan existing local image" echo " pull-scan - Pull image from registry and scan" exit 1 ;; esac

Make scanning script executable

Set proper permissions on the scanning wrapper script.

sudo chmod 755 /usr/local/bin/podman-scan
sudo chown root:root /usr/local/bin/podman-scan

Install jq for JSON parsing

Install jq utility for parsing Trivy JSON output in the scanning script.

sudo apt install -y jq
sudo dnf install -y jq

Implement automated vulnerability scanning workflows

Create systemd timer for database updates

Set up automatic vulnerability database updates to ensure current CVE data.

[Unit]
Description=Update Trivy vulnerability database
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=root
ExecStart=/usr/bin/trivy image --download-db-only
Environment=TRIVY_CACHE_DIR=/var/lib/trivy

[Install]
WantedBy=multi-user.target

Create systemd timer configuration

Schedule daily vulnerability database updates during low-traffic hours.

[Unit]
Description=Update Trivy database daily
Requires=trivy-db-update.service

[Timer]
OnCalendar=daily
RandomizedDelaySec=3600
Persistent=true

[Install]
WantedBy=timers.target

Enable and start the update timer

Activate the systemd timer for automatic vulnerability database updates.

sudo systemctl daemon-reload
sudo systemctl enable trivy-db-update.timer
sudo systemctl start trivy-db-update.timer
sudo systemctl status trivy-db-update.timer

Create CI/CD integration script

Build a script for integrating security scanning into continuous integration pipelines.

#!/bin/bash

CI/CD security scanning integration

set -e

Configuration

FAIL_ON_CRITICAL=${FAIL_ON_CRITICAL:-true} FAIL_ON_HIGH=${FAIL_ON_HIGH:-false} MAX_HIGH_VULNS=${MAX_HIGH_VULNS:-10} SCAN_TIMEOUT=${SCAN_TIMEOUT:-300} OUTPUT_FORMAT=${OUTPUT_FORMAT:-json}

Function to scan and evaluate

ci_scan() { local image="$1" local build_id="${2:-$(date +%s)}" local output_file="/tmp/trivy-ci-${build_id}.json" echo "=== Security Scan Report ===" echo "Image: $image" echo "Build ID: $build_id" echo "Timestamp: $(date)" echo "==============================" # Run security scan timeout "$SCAN_TIMEOUT" trivy image \ --format json \ --output "$output_file" \ --severity HIGH,CRITICAL \ --no-progress \ "$image" || { echo "ERROR: Scan failed or timed out" exit 1 } # Parse results if [ -f "$output_file" ]; then critical_vulns=$(jq -r '.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL") | .VulnerabilityID' "$output_file" 2>/dev/null | wc -l) high_vulns=$(jq -r '.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH") | .VulnerabilityID' "$output_file" 2>/dev/null | wc -l) echo "Critical vulnerabilities: $critical_vulns" echo "High vulnerabilities: $high_vulns" # Display summary table trivy image \ --format table \ --severity HIGH,CRITICAL \ --no-progress \ "$image" # Apply CI/CD policies exit_code=0 if [ "$FAIL_ON_CRITICAL" = "true" ] && [ "$critical_vulns" -gt 0 ]; then echo "❌ FAIL: Critical vulnerabilities found (Policy: FAIL_ON_CRITICAL=true)" exit_code=1 fi if [ "$FAIL_ON_HIGH" = "true" ] && [ "$high_vulns" -gt "$MAX_HIGH_VULNS" ]; then echo "❌ FAIL: Too many high-severity vulnerabilities ($high_vulns > $MAX_HIGH_VULNS)" exit_code=1 fi if [ $exit_code -eq 0 ]; then echo "✅ PASS: Security scan completed successfully" fi # Copy results to permanent location if specified if [ -n "$SCAN_RESULTS_DIR" ]; then mkdir -p "$SCAN_RESULTS_DIR" cp "$output_file" "$SCAN_RESULTS_DIR/scan-${build_id}.json" fi # Cleanup rm -f "$output_file" return $exit_code else echo "ERROR: Scan output file not found" return 1 fi }

Main execution

if [ $# -lt 1 ]; then echo "Usage: $0 [build_id]" echo "Environment variables:" echo " FAIL_ON_CRITICAL=true|false (default: true)" echo " FAIL_ON_HIGH=true|false (default: false)" echo " MAX_HIGH_VULNS=N (default: 10)" echo " SCAN_RESULTS_DIR=path (optional)" exit 1 fi ci_scan "$1" "$2"

Make CI script executable

Set proper permissions on the CI/CD integration script.

sudo chmod 755 /usr/local/bin/ci-security-scan
sudo chown root:root /usr/local/bin/ci-security-scan

Set up security policies and reporting

Create security policy configuration

Define organizational security policies for container image scanning and compliance.

# Container Security Policy Configuration
policy:
  # Vulnerability severity thresholds
  severity_thresholds:
    critical: 0          # Block deployment if any critical vulns
    high: 5             # Block if more than 5 high vulns
    medium: 20          # Warn if more than 20 medium vulns
    low: 50             # Info if more than 50 low vulns
  
  # Scanner types to enable
  scanners:
    - vuln              # CVE vulnerabilities
    - secret            # Exposed secrets/keys
    - config            # Configuration issues
  
  # Base image policies
  base_images:
    approved:
      - "docker.io/library/ubuntu:22.04"
      - "docker.io/library/alpine:3.18"
      - "registry.redhat.io/ubi8/ubi:latest"
    blocked:
      - "docker.io/library/ubuntu:16.04"  # EOL
      - "docker.io/library/centos:7"       # EOL
  
  # File scanning policies
  file_patterns:
    secrets:
      - "**/.env"
      - "**/config/*.key"
      - "/secrets/"
    configs:
      - "**/nginx.conf"
      - "**/apache2.conf"
      - "**/ssh_config"
  
  # Compliance requirements
  compliance:
    require_signed_images: true
    block_unpatched_critical: true
    max_image_age_days: 90
    require_minimal_base: false

Create vulnerability report generator

Build a comprehensive reporting script for security scan results and trends.

#!/bin/bash

Security vulnerability reporting tool

set -e

Configuration

REPORT_DIR="/var/log/trivy/reports" DATE=$(date +%Y-%m-%d) REPORT_FILE="${REPORT_DIR}/security-report-${DATE}.html" JSON_FILE="${REPORT_DIR}/security-report-${DATE}.json"

Create report directory

mkdir -p "$REPORT_DIR"

Function to scan multiple images

scan_images() { local images=("$@") local results="[]" for image in "${images[@]}"; do echo "Scanning $image..." # Scan image scan_file="/tmp/scan-$(echo "$image" | tr '/:' '_').json" trivy image --format json --output "$scan_file" "$image" || { echo "Failed to scan $image" continue } # Extract summary if [ -f "$scan_file" ]; then critical=$(jq -r '.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL") | .VulnerabilityID' "$scan_file" 2>/dev/null | wc -l) high=$(jq -r '.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH") | .VulnerabilityID' "$scan_file" 2>/dev/null | wc -l) medium=$(jq -r '.Results[]?.Vulnerabilities[]? | select(.Severity == "MEDIUM") | .VulnerabilityID' "$scan_file" 2>/dev/null | wc -l) low=$(jq -r '.Results[]?.Vulnerabilities[]? | select(.Severity == "LOW") | .VulnerabilityID' "$scan_file" 2>/dev/null | wc -l) # Build JSON result result=$(jq -n \ --arg image "$image" \ --arg critical "$critical" \ --arg high "$high" \ --arg medium "$medium" \ --arg low "$low" \ --arg timestamp "$(date -Iseconds)" \ '{ image: $image, timestamp: $timestamp, vulnerabilities: { critical: ($critical | tonumber), high: ($high | tonumber), medium: ($medium | tonumber), low: ($low | tonumber) } }') results=$(echo "$results" | jq --argjson new "$result" '. + [$new]') rm -f "$scan_file" fi done echo "$results" }

Function to generate HTML report

generate_html_report() { local json_data="$1" cat > "$REPORT_FILE" << EOF Container Security Report - $DATE

Container Security Report

Generated: $(date)

Summary

EOF # Calculate totals total_images=$(echo "$json_data" | jq '. | length') total_critical=$(echo "$json_data" | jq '[.[].vulnerabilities.critical] | add') total_high=$(echo "$json_data" | jq '[.[].vulnerabilities.high] | add') total_medium=$(echo "$json_data" | jq '[.[].vulnerabilities.medium] | add') total_low=$(echo "$json_data" | jq '[.[].vulnerabilities.low] | add') cat >> "$REPORT_FILE" << EOF

Total Images Scanned: $total_images

Total Critical Vulnerabilities: $total_critical

Total High Vulnerabilities: $total_high

Total Medium Vulnerabilities: $total_medium

Total Low Vulnerabilities: $total_low

Detailed Results

EOF # Add table rows echo "$json_data" | jq -r '.[] | ""' >> "$REPORT_FILE" cat >> "$REPORT_FILE" << EOF
Image Critical High Medium Low Scan Time
\(.image)\(.vulnerabilities.critical)\(.vulnerabilities.high)\(.vulnerabilities.medium)\(.vulnerabilities.low)\(.timestamp)
EOF }

Main execution

if [ $# -eq 0 ]; then echo "Usage: $0 [image2] [image3] ..." echo "Example: $0 nginx:latest alpine:latest ubuntu:22.04" exit 1 fi echo "Generating security report for ${#@} images..."

Scan all provided images

results=$(scan_images "$@")

Save JSON results

echo "$results" | jq '.' > "$JSON_FILE"

Generate HTML report

generate_html_report "$results" echo "Reports generated:" echo " JSON: $JSON_FILE" echo " HTML: $REPORT_FILE"

Display summary

echo echo "Summary:" echo "$results" | jq -r '.[] | "\(.image): \(.vulnerabilities.critical) critical, \(.vulnerabilities.high) high"'

Make report generator executable

Set proper permissions on the security report generation script.

sudo chmod 755 /usr/local/bin/generate-security-report
sudo chown root:root /usr/local/bin/generate-security-report

Create log rotation configuration

Set up log rotation to manage Trivy scan logs and prevent disk space issues.

/var/log/trivy/*.log {
    daily
    missingok
    rotate 30
    compress
    delaycompress
    notifempty
    create 0644 root root
    postrotate
        /usr/bin/systemctl reload rsyslog > /dev/null 2>&1 || true
    endscript
}

/var/log/trivy/reports/*.json {
    weekly
    missingok
    rotate 12
    compress
    delaycompress
    notifempty
    create 0644 root root
}

Verify your setup

Test basic Trivy functionality

Verify that Trivy can scan images and detect vulnerabilities.

trivy --version
trivy image --severity HIGH,CRITICAL alpine:latest

Test Podman scanning integration

Use the scanning wrapper script to test the integration workflow.

podman-scan pull-scan nginx:latest
echo $?

Test CI/CD integration script

Verify the CI/CD scanning script works with different policy configurations.

FAIL_ON_CRITICAL=true ci-security-scan alpine:latest test-build-001
FAIL_ON_HIGH=true MAX_HIGH_VULNS=0 ci-security-scan ubuntu:latest test-build-002

Generate a sample security report

Create a comprehensive security report to verify reporting functionality.

generate-security-report nginx:latest alpine:latest ubuntu:22.04
ls -la /var/log/trivy/reports/

Check systemd timer status

Verify that the vulnerability database update timer is functioning correctly.

sudo systemctl status trivy-db-update.timer
sudo systemctl list-timers trivy-db-update.timer

Common issues

Symptom Cause Fix
Database download fails Network connectivity or firewall Check internet access and proxy settings: curl -I https://github.com
Permission denied on scan files Incorrect directory ownership Fix ownership: sudo chown -R $USER:$USER /var/lib/trivy
JSON parsing errors in scripts Missing jq package or malformed JSON Install jq and validate JSON: sudo apt install -y jq
CI scan timeouts Large images or slow network Increase timeout: export SCAN_TIMEOUT=600
Systemd timer not running Timer not enabled or service failed Check logs: sudo systemctl status trivy-db-update.service
High memory usage during scans Large container images Scan images individually and use --light flag
False positive vulnerabilities Outdated vulnerability database Update database: trivy image --download-db-only
Report generation fails Missing write permissions Create directory: sudo mkdir -p /var/log/trivy/reports && sudo chown $USER:$USER /var/log/trivy/reports

Next steps

Running this in production?

Want this handled for you? Setting this up once is straightforward. Keeping it patched, monitored, backed up and performant across environments is the harder part. See how we run infrastructure like this for European teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle infrastructure security hardening for businesses that depend on uptime. From initial setup to ongoing operations.