Scanning Docker images using Trivly within Azure DevOps


Index -

Container scanning using Trivy

Trivy is an open-source security scanner that can be used to find vulnerabilities and other security issues within images (such as secrets stored inside config)

In my case I integrated it into my existing CI/CD pipeline so that my Docker images are scanned for vulnerabilities before deployment

Pipeline config

To add Trivy into a pipeline there are 2 steps - installing and then running the scan

To install its simply a case of running the Linux installer

To run a scan you call the trivy image command with additional flags:

  • —exit-code 1 —severity HIGH,CRITICAL
    This is used so that any high & critical vulnerabilities cause the program to exit with an error code - in turn this causes the pipeline to fail and in my config this means that the web app will not be uploaded
    You can also use —exit-code 0 —severity HIGH,CRITICAL,MEDIUM,LOW to generate a report of all vulnerabilities

  • $(containerRegistry)/$(imageRepository):$(tag)
    This points to the image storage within the ACR (Azure Container Registry) in the format of {container registry}/{image name}:{tag}

Additionally you will need to also specify a few environmental variables using the ‘env’ section

This is used to specify the login details for my ACR - the open-source (and free) version is limited to supporting basic auth methods when pulling the image from the ACR
Paid plans additionally also support managed identity auth so you will not need to have explicit usernames & passwords

  - stage: Trivy
    displayName: 'Install trivy and run a scan'
    jobs:
    - job: Trivy
      displayName: Install Trivy and run a scan
      steps:
      - script: |
              sudo apt-get install rpm
              wget https://github.com/aquasecurity/trivy/releases/download/v0.65.0/trivy_0.65.0_Linux-64bit.deb
              sudo dpkg -i trivy_0.65.0_Linux-64bit.deb
              trivy -v
      - task: Bash@3
        displayName: 'run scan'
        inputs:
          targetType: 'inline'
          script: |
            trivy image --exit-code 1 --severity HIGH,CRITICAL $(containerRegistryURL)/$(imageRepository):$(tag)
        env:
          TRIVY_AUTH_URL: "https://$(containerRegistryURL)"
          TRIVY_USERNAME: $(containerRegistryName)
          TRIVY_PASSWORD: $(containerPassword)

To ensure that my pipeline exits when the scan flags up vulnerabilites I also added a dependency portion to the pubish stage so it will not run unless the scan stage succeeds

  - stage: Publish
    condition: succeeded('Trivy')
    dependsOn: Trivy
Full azure-pipelines.yml
# Docker

pool:
  vmImage: 'ubuntu-latest'

resources:
- repo: self

variables:
- group: staticLib


stages:
  - stage: Build
    displayName: Build and push stage
    jobs:
    - job: Build
      displayName: Build
      steps:
      - task: Docker@2
        displayName: Build and push an image to container registry
        inputs:
          command: buildAndPush
          repository: $(imageRepository)
          dockerfile: $(dockerfilePath)
          containerRegistry: $(dockerRegistryServiceConnection)
          tags: |
            $(tag)

  - stage: Trivy
    displayName: 'Install trivy and run a scan'
    jobs:
    - job: Trivy
      displayName: Install Trivy and run a scan
      steps:
      - script: |
              sudo apt-get install rpm
              wget https://github.com/aquasecurity/trivy/releases/download/v0.65.0/trivy_0.65.0_Linux-64bit.deb
              sudo dpkg -i trivy_0.65.0_Linux-64bit.deb
              trivy -v
      - task: Bash@3
        displayName: 'run scan'
        inputs:
          targetType: 'inline'
          script: |
            trivy image --exit-code 1 --severity HIGH,CRITICAL $(containerRegistryURL)/$(imageRepository):$(tag)
        env:
          TRIVY_AUTH_URL: "https://$(containerRegistryURL)"
          TRIVY_USERNAME: $(containerRegistryName)
          TRIVY_PASSWORD: $(containerPassword)

  - stage: Publish
    condition: succeeded('Trivy')
    dependsOn: Trivy
    displayName: 'publish container'
    jobs:
    - job: 
      displayName: Publish container
      steps:
      - task: AzureWebAppContainer@1
        displayName: 'Azure Web App on Container Deploy'
        inputs:
          azureSubscription: $(azureSubscription)
          appName: $(appName)
          containers: $(containerRegistry)/$(imageRepository):$(tag)

Fixing my own vulnerabilities

As a results of adding container scanning this revealed high vulnerabilities within my own deployment
highvuln

When investigating my dockerfile I found explicit mentions to out of date versions:

FROM node:22-alpine AS builder
FROM nginx:1.27-alpine AS runtime

This was a simple fix but without any automated scanning/reporting I probably wouldn’t have found found this security flaw
FROM node:22-alpine AS builder —> FROM node:latest AS builder
FROM nginx:1.27-alpine AS runtime —> FROM nginx:mainline-alpine AS runtime

After correcting this I ran the pipeline again and it ran fully and deployed my app as expected

novuln

full run