Set up Kubernetes container image security scanning with Trivy and admission controllers

Advanced 45 min Jun 07, 2026 43 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Implement automated security scanning for Kubernetes container images using Trivy scanner and admission controllers to block vulnerable images before deployment.

Prerequisites

  • Kubernetes cluster with admin access
  • kubectl configured and working
  • Internet connectivity for downloading vulnerability databases
  • At least 4GB RAM available for scanning workloads

What this solves

Container images often contain vulnerabilities that can compromise your Kubernetes cluster. This tutorial sets up Trivy as a vulnerability scanner integrated with admission controllers to automatically scan and block insecure container images before they reach production. You'll configure the Trivy Operator to continuously monitor running containers and create security policies that prevent deployments with critical vulnerabilities.

Step-by-step installation

Install Trivy binary

Install the Trivy scanner binary for standalone scanning capabilities and initial testing.

sudo apt-get update
wget https://github.com/aquasecurity/trivy/releases/download/v0.48.1/trivy_0.48.1_Linux-64bit.deb
sudo dpkg -i trivy_0.48.1_Linux-64bit.deb
sudo apt-get install -f
sudo dnf update -y
wget https://github.com/aquasecurity/trivy/releases/download/v0.48.1/trivy_0.48.1_Linux-64bit.rpm
sudo rpm -ivh trivy_0.48.1_Linux-64bit.rpm

Test Trivy standalone scanning

Verify Trivy works correctly by scanning a test container image to see vulnerability reporting.

trivy image nginx:latest
trivy image --severity HIGH,CRITICAL nginx:latest

Install Helm for Kubernetes deployments

Install Helm package manager to deploy the Trivy Operator and other Kubernetes components.

curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh

Create namespace for Trivy Operator

Create a dedicated namespace to isolate the Trivy Operator and its components.

kubectl create namespace trivy-system
kubectl label namespace trivy-system security=scanning

Deploy Trivy Operator

Install the Trivy Operator using Helm to enable automated scanning within the cluster.

helm repo add aqua https://aquasecurity.github.io/helm-charts/
helm repo update
helm install trivy-operator aqua/trivy-operator \
  --namespace trivy-system \
  --create-namespace \
  --set operator.scanJobsConcurrentLimit=3 \
  --set vulnerabilityReports.scanner=Trivy \
  --set compliance.failureThreshold=medium

Configure Trivy scanning policies

Create a configuration to define which vulnerability levels block deployments.

apiVersion: v1
kind: ConfigMap
metadata:
  name: trivy-operator-config
  namespace: trivy-system
data:
  trivy.severity: "HIGH,CRITICAL"
  trivy.ignoreUnfixed: "true"
  trivy.timeout: "5m0s"
  trivy.dbRepository: "ghcr.io/aquasecurity/trivy-db"
  vulnerabilityReports.scanner: "Trivy"
  configAuditReports.scanner: "Trivy"
kubectl apply -f /etc/kubernetes/trivy-config.yaml

Install OPA Gatekeeper for admission control

Deploy OPA Gatekeeper to enforce security policies and integrate with Trivy scan results.

kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.14/deploy/gatekeeper.yaml
kubectl wait --for=condition=Ready --timeout=300s -n gatekeeper-system pod -l control-plane=controller-manager

Create security policy template

Define a Gatekeeper constraint template that enforces image vulnerability scanning requirements.

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8srequiresecurityscan
spec:
  crd:
    spec:
      names:
        kind: K8sRequireSecurityScan
      validation:
        type: object
        properties:
          maxCritical:
            type: integer
            minimum: 0
          maxHigh:
            type: integer
            minimum: 0
          exemptImages:
            type: array
            items:
              type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiresecurityscan
        
        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          image := container.image
          not exempt_image(image)
          msg := sprintf("Container image %v requires security scanning before deployment", [image])
        }
        
        exempt_image(image) {
          exemptions := input.parameters.exemptImages
          exemption := exemptions[_]
          startswith(image, exemption)
        }
kubectl apply -f /etc/kubernetes/security-policy-template.yaml

Create admission controller constraint

Apply a constraint that blocks deployments with images containing critical or high vulnerabilities.

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireSecurityScan
metadata:
  name: must-scan-images
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
      - apiGroups: [""]
        kinds: ["Pod"]
    excludedNamespaces: ["kube-system", "trivy-system", "gatekeeper-system"]
  parameters:
    maxCritical: 0
    maxHigh: 5
    exemptImages:
      - "gcr.io/distroless/"
      - "registry.k8s.io/"
kubectl apply -f /etc/kubernetes/security-constraint.yaml

Configure automated scanning webhook

Set up a validating admission webhook that integrates Trivy scanning with Kubernetes API.

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionWebhook
metadata:
  name: trivy-scan-webhook
webhooks:
  • name: image-scan.trivy.aquasec.com
clientConfig: service: name: trivy-operator-webhook namespace: trivy-system path: "/validate" rules: - operations: ["CREATE", "UPDATE"] apiGroups: ["apps"] apiVersions: ["v1"] resources: ["deployments"] - operations: ["CREATE", "UPDATE"] apiGroups: [""] apiVersions: ["v1"] resources: ["pods"] admissionReviewVersions: ["v1", "v1beta1"] sideEffects: None failurePolicy: Fail
kubectl apply -f /etc/kubernetes/scanning-webhook.yaml

Create security monitoring alerts

Configure alerts to notify when vulnerabilities are detected or policies are violated.

apiVersion: v1
kind: ConfigMap
metadata:
  name: trivy-alerts-config
  namespace: trivy-system
data:
  alerts.yaml: |
    groups:
    - name: trivy.rules
      rules:
      - alert: HighVulnerabilityFound
        expr: trivy_vulnerability_count{severity="HIGH"} > 0
        for: 0m
        labels:
          severity: warning
        annotations:
          summary: "High vulnerability detected"
          description: "Image {{ $labels.image }} has {{ $value }} high vulnerabilities"
      - alert: CriticalVulnerabilityFound
        expr: trivy_vulnerability_count{severity="CRITICAL"} > 0
        for: 0m
        labels:
          severity: critical
        annotations:
          summary: "Critical vulnerability detected"
          description: "Image {{ $labels.image }} has {{ $value }} critical vulnerabilities"
kubectl apply -f /etc/kubernetes/security-alerts.yaml

Configure scan scheduling

Set up automated recurring scans to check for new vulnerabilities in deployed images.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: trivy-cluster-scan
  namespace: trivy-system
spec:
  schedule: "0 2   *"  # Daily at 2 AM
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: trivy-operator
          containers:
          - name: trivy-scanner
            image: aquasec/trivy:0.48.1
            command:
            - /bin/sh
            - -c
            - |
              kubectl get pods --all-namespaces -o jsonpath='{range .items[]}{.spec.containers[].image}{"\n"}{end}' | \
              sort -u | \
              while read image; do
                echo "Scanning $image"
                trivy image --format json --output /tmp/scan-results.json "$image"
              done
            volumeMounts:
            - name: scan-results
              mountPath: /tmp
          volumes:
          - name: scan-results
            emptyDir: {}
          restartPolicy: OnFailure
kubectl apply -f /etc/kubernetes/scan-schedule.yaml

Verify your setup

# Check Trivy Operator status
kubectl get pods -n trivy-system
kubectl logs -n trivy-system deployment/trivy-operator

Test scanning a vulnerable image

kubectl create deployment test-nginx --image=nginx:1.14-alpine

Check for vulnerability reports

kubectl get vulnerabilityreports kubectl get configauditreports

Test admission controller

kubectl run test-vulnerable --image=vulnerables/web-dvwa --dry-run=server

Verify Gatekeeper constraints

kubectl get constraints kubectl describe k8srequiresecurityscan must-scan-images

Configure security policies

Create namespace-specific policies

Define different security requirements for development, staging, and production namespaces.

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireSecurityScan
metadata:
  name: production-strict-scanning
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
    namespaces: ["production", "staging"]
  parameters:
    maxCritical: 0
    maxHigh: 0
    exemptImages: []
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireSecurityScan
metadata:
  name: development-relaxed-scanning
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
    namespaces: ["development"]
  parameters:
    maxCritical: 2
    maxHigh: 10
    exemptImages:
      - "docker.io/library/"
      - "ghcr.io/"
kubectl apply -f /etc/kubernetes/production-policy.yaml

Configure custom scan policies

Create specific scanning rules for different types of vulnerabilities and compliance requirements.

apiVersion: v1
kind: ConfigMap
metadata:
  name: trivy-custom-policy
  namespace: trivy-system
data:
  policy.rego: |
    package trivy
    
    default allow = false
    
    allow {
      count(high_vulnerabilities) == 0
      count(critical_vulnerabilities) == 0
      not contains_malware
      not contains_secrets
    }
    
    high_vulnerabilities[vuln] {
      vuln := input.Results[_].Vulnerabilities[_]
      vuln.Severity == "HIGH"
      not vuln.FixedVersion == ""
    }
    
    critical_vulnerabilities[vuln] {
      vuln := input.Results[_].Vulnerabilities[_]
      vuln.Severity == "CRITICAL"
    }
    
    contains_malware {
      input.Results[_].Class == "malware"
    }
    
    contains_secrets {
      input.Results[_].Class == "secret"
    }
kubectl apply -f /etc/kubernetes/custom-scan-policy.yaml
kubectl patch configmap trivy-operator-config -n trivy-system --patch '{"data":{"policy.bundle.url":"configmap://trivy-system/trivy-custom-policy"}}'

Set up monitoring and alerts

Note: For comprehensive monitoring integration, you can reference our guide on monitoring Kubernetes with Prometheus Operator for metrics collection.

Configure Prometheus monitoring

Set up metrics collection for security scanning and policy enforcement.

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: trivy-operator-metrics
  namespace: trivy-system
  labels:
    app: trivy-operator
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: trivy-operator
  endpoints:
  - port: metrics
    interval: 30s
    path: /metrics
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: trivy-security-alerts
  namespace: trivy-system
spec:
  groups:
  - name: trivy.security
    rules:
    - alert: VulnerabilityReportFailed
      expr: increase(trivy_vulnerability_scan_errors_total[5m]) > 0
      for: 0m
      labels:
        severity: warning
      annotations:
        summary: "Vulnerability scan failed"
        description: "Trivy vulnerability scan has failed {{ $value }} times in the last 5 minutes"
    - alert: CriticalVulnerabilityDetected
      expr: trivy_image_vulnerabilities{severity="Critical"} > 0
      for: 0m
      labels:
        severity: critical
      annotations:
        summary: "Critical vulnerability in image"
        description: "Image {{ $labels.image_repository }}:{{ $labels.image_tag }} has {{ $value }} critical vulnerabilities"
kubectl apply -f /etc/kubernetes/trivy-servicemonitor.yaml

Common issues

Symptom Cause Fix
Trivy scans timeout Database download or network issues kubectl patch configmap trivy-operator-config -n trivy-system --patch '{"data":{"trivy.timeout":"10m0s"}}'
Admission controller blocks all deployments Webhook configuration error kubectl delete validatingadmissionwebhook trivy-scan-webhook then reconfigure
Vulnerability reports not generated RBAC permissions missing kubectl describe clusterrolebinding trivy-operator to check permissions
OPA Gatekeeper policies not enforced Constraint template syntax error kubectl describe constrainttemplate k8srequiresecurityscan for validation errors
High memory usage during scans Concurrent scan limit too high Reduce scanJobsConcurrentLimit in Helm values and upgrade

Next steps

Running this in production?

Want this handled for you? Running container security at scale adds a second layer of work: capacity planning for scan jobs, failover drills for admission controllers, cost control for vulnerability databases, and on-call expertise. 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.