Introduction
Welcome back! Last time, you learned how to build and push your Docker image to Google Artifact Registry (GAR) using GitLab CI/CD. But before we ship our app to production, let’s make sure our Docker image is safe by scanning it for vulnerabilities using a tool called Trivy.
In this episode, you’ll learn:
- What Trivy is and why you need image scanning
- How to add Trivy to your GitLab CI/CD pipeline
- How to read and act on your scan results
1. What is Trivy and Why Scan Your Images?
Trivy is an easy-to-use, open-source tool that scans Docker images for security vulnerabilities.
Just like you wouldn’t install software from an unknown source, you shouldn’t run containers with hidden risks.
Why scan?
- Containers often include software from many sources
- Vulnerabilities can sneak into your images even if your code is safe
- Scanning helps you find and fix risks before they hit production
2. How Trivy Fits in Your Pipeline
Here’s how your pipeline flow looks now:
[GitLab Pipeline]
|
v
[Build Docker Image]
|
v
[Push Image to GAR]
|
v
[Scan Image with Trivy]
|
v
[Ready for Deploy!]
3. Adding Trivy to Your .gitlab-ci.yml
Here’s a simple Trivy job based on your previous setup:
image_scan:
stage: sca_image
image: google/cloud-sdk:latest
variables:
TRIVY_VERSION: "0.54.1"
before_script:
- echo "Install Docker"
- apt-get update && apt-get install -y docker.io curl ca-certificates tar gzip
- echo "Setting up Google Cloud authentication..."
- echo "$GCP_SA_KEY" | base64 -d > $CI_PROJECT_DIR/gcp-key.json
- gcloud auth activate-service-account --key-file=$CI_PROJECT_DIR/gcp-key.json
- gcloud config set project YOUR_PROJECT_ID
- gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet
- curl -fsSL https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz | tar -xz -C /usr/local/bin trivy
- trivy --version
script:
- echo "Scanning Docker image for vulnerabilities..."
- trivy image $IMAGE_TAG_GOOGLE | tee trivy-report.txt
allow_failure: true
artifacts:
name: "trivy-report"
paths:
- trivy-report.txt
expire_in: 1 week
What’s happening here?
- Installs Docker and Trivy
- Authenticates with Google Cloud so the image can be pulled
- Runs Trivy to scan your image in GAR
- Saves the results as
trivy-report.txt
- The job is allowed to fail (
allow_failure: true
) so a vulnerability won’t block your learning or experimenting
4. Merge scripts together
It will look like this
image: docker:latest
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2375/
DOCKER_TLS_CERTDIR: ""
GO_VERSION: "1.24.3"
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
IMAGE_NAME_GOOGLE: "booranasak-bank-api-golang"
IMAGE_VERSION_TAG: "1.0.0"
IMAGE_TAG_GOOGLE: "asia-southeast1-docker.pkg.dev/escian/booranasak-artifact-registry-docker/$IMAGE_NAME_GOOGLE:$IMAGE_VERSION_TAG"
IMAGE_TAR: todo-api.tar
GIT_DEPTH: "0"
stages:
- lint
- test
- sast
- build
- push
- sca_image
.go-job-template: &go-job-template
image: debian:bullseye
before_script:
- apt update && apt install -y curl git tar gzip
- curl -LO https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz
- rm -rf /usr/local/go && tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz
- export PATH="/usr/local/go/bin:$PATH"
- go version
lint_golint:
stage: lint
<<: *go-job-template
script:
- export PATH="/usr/local/go/bin:$PATH"
- go install golang.org/x/lint/golint@latest
- export PATH="$PATH:$(go env GOPATH)/bin"
- echo "Linting files:"
- find . -name '*.go'
- golint ./... | tee lint-report.txt
- echo "--- Lint report preview ---"
- cat lint-report.txt || echo "lint-report.txt is empty"
allow_failure: true
artifacts:
name: "golint-report"
paths:
- lint-report.txt
expire_in: 1 week
unit_test_and_coverage:
stage: test
<<: *go-job-template
script:
- export PATH="/usr/local/go/bin:$PATH"
- go mod tidy
- go test -v -cover ./...
- go test -v -coverprofile=coverage.out ./...
artifacts:
paths:
- coverage.out
expire_in: 1 hour
sonarqube-check:
stage: sast
image:
name: sonarsource/sonar-scanner-cli:11
entrypoint: [""]
dependencies:
- unit_test_and_coverage
script:
- sonar-scanner -Dsonar.host.url="${SONAR_HOST_URL}" -Dsonar.go.coverage.reportPaths=coverage.out -Dsonar.exclusions=**/*_test.go
allow_failure: true
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
- if: $CI_COMMIT_BRANCH == 'master'
- if: $CI_COMMIT_BRANCH == 'main'
- if: $CI_COMMIT_BRANCH == 'develop'
sonarqube-vulnerability-report:
stage: sast
image:
name: sonarsource/sonar-scanner-cli:11
entrypoint: [""]
script:
- 'curl -u "${SONAR_TOKEN}:" "${SONAR_HOST_URL}/api/issues/gitlab_sast_export?projectKey=booranasak-golang-sast&branch=${CI_COMMIT_BRANCH}&pullRequest=${CI_MERGE_REQUEST_IID}" -o gl-sast-sonar-report.json'
allow_failure: true
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
- if: $CI_COMMIT_BRANCH == 'master'
- if: $CI_COMMIT_BRANCH == 'main'
- if: $CI_COMMIT_BRANCH == 'develop'
artifacts:
expire_in: 1 day
reports:
sast: gl-sast-sonar-report.json
build_image:
stage: build
image: docker:latest
script:
- docker build -t $IMAGE_TAG_GOOGLE .
- docker save $IMAGE_TAG_GOOGLE -o $IMAGE_TAR
artifacts:
name: "docker-image-tar"
paths:
- $IMAGE_TAR
expire_in: 1 hour
push_image_to_registry:
stage: push
image: google/cloud-sdk:latest
dependencies:
- build_image
before_script:
- echo "Install Docker"
- apt-get update && apt-get install -y docker.io
- echo "Setting up Google Cloud authentication..."
- echo "$GCP_SA_KEY" | base64 -d > $CI_PROJECT_DIR/gcp-key.json
- gcloud auth activate-service-account --key-file=$CI_PROJECT_DIR/gcp-key.json
- gcloud config set project escian
- gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet
script:
- docker load -i $IMAGE_TAR
- docker push $IMAGE_TAG_GOOGLE
image_scan:
stage: sca_image
image: google/cloud-sdk:latest
variables:
TRIVY_VERSION: "0.54.1"
before_script:
- echo "Install Docker"
- apt-get update && apt-get install -y docker.io curl ca-certificates tar gzip
- echo "Setting up Google Cloud authentication..."
- echo "$GCP_SA_KEY" | base64 -d > $CI_PROJECT_DIR/gcp-key.json
- gcloud auth activate-service-account --key-file=$CI_PROJECT_DIR/gcp-key.json
- gcloud config set project escian
- gcloud auth configure-docker asia-southeast1-docker.pkg.dev --quiet
- curl -fsSL https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz | tar -xz -C /usr/local/bin trivy
- trivy --version
script:
- echo "Scanning Docker image for vulnerabilities..."
- trivy image $IMAGE_TAG_GOOGLE
allow_failure: true
5. Viewing and Understanding Your Trivy Scan Results
- Go to your pipeline in GitLab.
- Click at your recent run pipeline
- Click on the
image_scan
job.
*You can view the Trivy report directly from the output logs, or configure your pipeline to save the Trivy report as an artifact. This allows you to download or preview the trivy-report.txt artifact.
Output logs(this from other pipeline)
What do the results mean?
- Trivy will list all detected vulnerabilities, their severity, and where they were found (like OS packages or app dependencies).
- Focus on “HIGH” or “CRITICAL” vulnerabilities—those should be fixed first.
- If the Status is fixed, you’re already protected and don’t need to take action.
- But if the status is affected, will_not_fix, or fix_deferred, you should pay close attention—these vulnerabilities are not yet resolved and may still put your project at risk. Review them carefully and patch or mitigate as soon as possible.
In our Code and Image that being use there are no vulnerabilities
6. What If I Find Vulnerabilities?
- Read the Trivy output carefully—it tells you which package (and version) is affected.
- Many issues can be fixed by updating your
Dockerfile
or base image. - Sometimes, vulnerabilities are in dependencies you don’t use directly. Research if an update or patch is available.
- If you’re not sure, search the CVE ID online for advice.
7. Tips and Best Practices
- Scan every new build: Don’t skip scanning for speed; automate it!
- Keep Trivy updated: Vulnerabilities are found all the time.
- Don’t ignore “HIGH”/“CRITICAL” issues: If you must, at least understand the risk.
- Automate alerting: Advanced: send scan failures to Slack, email, etc.
8. What’s Next?
Awesome! Your pipeline now checks for vulnerabilities in every image.
Next, we’ll move on to deploying your image and running live API security scans with OWASP ZAP—so your app is not only safe inside, but also from the outside.
Summary
- Added Trivy to your pipeline for automatic container security checks
- Learned how to read and act on Trivy results
- Set your project up for safer, more professional deployments
Ready for the next step? See you in Episode 6 for deploy and API security scanning with ZAP!
Top comments (0)