Skip to main content
Security February 25, 2026 13 min read

Tutorial: Securing Docker Containers with Trivy for SMBs

Scan your Docker images with Trivy in under 10 minutes. A practical tutorial to protect your SMB infrastructure without a cybersecurity background.

M
Mohamed Boukri

Why Docker images are an entry point for attackers

SMBs are adopting Docker at scale — often through tools like Coolify to self-host their applications. That’s a sound operational decision. But there’s a common blind spot: public images pulled from Docker Hub bundle hundreds of system packages, and some of those packages contain known vulnerabilities.

This is what a supply-chain attack looks like in a container context. You’re not intentionally downloading malicious code — you pull node:18 or nginx:latest, and that image ships a version of OpenSSL with a critical flaw you never noticed. An attacker who exploits that flaw can potentially compromise your entire server.

Trivy is the practical answer to this problem. It’s a free, open-source scanner maintained by Aqua Security that analyzes your Docker images, configuration files, and application dependencies in seconds. No cybersecurity expertise required.

This tutorial is aimed at developers, DevOps practitioners, and SMB owners who manage their infrastructure without a dedicated security team.

Prerequisites

  • Docker installed and running on your machine or server (Linux, macOS, or Windows with WSL2)
  • A terminal with sufficient permissions to run Docker commands
  • No prior security knowledge — Trivy installs in a single command
All commands in this tutorial were tested with Docker 25.x on Ubuntu 22.04 and macOS Sonoma.

Step 1: Install Trivy

The simplest way to get started is to use the official Aqua Security install script.

On Linux or macOS:

Terminal window
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.59.1

This command downloads and installs Trivy directly into /usr/local/bin. Pinning the version avoids surprises on future reinstalls.

No local install needed — run Trivy via Docker:

If you’d rather not install anything on the host machine, Trivy runs perfectly as a container:

Terminal window
docker run --rm aquasec/trivy:0.59.1 image nginx:latest

This approach is useful when you want to run a quick test on a production server without modifying the environment.

Verify the installation:

Terminal window
trivy --version
# Trivy version: 0.59.1

On the first scan, Trivy automatically downloads its CVE vulnerability database from the official repository. This takes 30 to 60 seconds depending on your connection. Subsequent runs use the local cache.

The CVE database is updated daily by Aqua Security. Force a refresh with trivy image --download-db-only if you haven’t run a scan in several days.

Step 2: Scan your first Docker image

Let’s start with a common image to understand the output format. Run a scan on nginx:latest:

Terminal window
trivy image nginx:latest

The report prints to the terminal. Here’s what each column means:

ColumnDescription
LibraryThe affected package or library
Vulnerability IDThe official CVE identifier (e.g. CVE-2024-3596)
SeverityCriticality level: CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN
Installed VersionThe version currently present in the image
Fixed VersionThe patched version available (empty if no fix exists)
TitleShort description of the vulnerability

Trivy separates vulnerabilities into two categories in its reports:

  • OS packages: flaws in system packages (libc, openssl, curl…) provided by the image’s base distribution
  • Application dependencies: flaws in your code’s dependencies (npm, pip, composer…)

OS vulnerabilities are often the most numerous but not always the most exploitable. Vulnerabilities in your application dependencies deserve priority attention because they directly affect your code.

Practical example with node:18:

Terminal window
trivy image node:18 --severity CRITICAL,HIGH

Filtering with --severity immediately cuts the noise. On an outdated node:18 image, you’ll typically find CRITICAL CVEs on packages like libssl or zlib. The report shows the Fixed Version — that’s your target version to patch.

Never use node:latest or nginx:latest in production without scanning the image first. The latest tag changes without warning and can introduce new vulnerabilities at any time.

Step 3: Scan your local images and those deployed with Coolify

Trivy works equally well on images pulled from a registry and on locally built images. Before pushing an image to production:

Terminal window
trivy image my-app:latest --severity CRITICAL,HIGH

Scan all images running on your Coolify server:

Start by listing the images currently in use:

Terminal window
docker images --format "{{.Repository}}:{{.Tag}}" | grep -v "<none>"

Then automate the scan with a simple Bash script:

scan-all-images.sh
#!/bin/bash
# Scan all Docker images and export results as JSON
OUTPUT_DIR="./trivy-reports/$(date +%Y-%m-%d)"
mkdir -p "$OUTPUT_DIR"
docker images --format "{{.Repository}}:{{.Tag}}" | grep -v "<none>" | while read image; do
safe_name=$(echo "$image" | tr '/:' '--')
echo "Scanning $image..."
trivy image \
--severity CRITICAL,HIGH \
--format json \
--output "$OUTPUT_DIR/${safe_name}.json" \
"$image"
done
echo "Reports exported to $OUTPUT_DIR"
Terminal window
chmod +x scan-all-images.sh
./scan-all-images.sh

Reports are exported as JSON into a timestamped folder. To prioritize fixes, focus on entries where FixedVersion is not empty — those are the vulnerabilities you can patch right now.

On a Coolify server, run this script from /home/coolify or a dedicated folder to keep reports organized.

Step 4: Fix the vulnerabilities Trivy found

Trivy found CRITICAL CVEs. Here are the three strategies to apply, in order.

Strategy 1 — Update the base image tag

This is the fastest fix. In your Dockerfile, replace the base image with a more recent version:

# Before
FROM node:18
# After — Alpine image, lighter with a smaller attack surface
FROM node:20-alpine

node:20-alpine ships Alpine Linux 3.x, which contains far fewer system packages than Debian or Ubuntu. Fewer packages means fewer potential vulnerabilities.

Strategy 2 — Switch to Distroless images

For production applications, Google’s Distroless images go even further than Alpine — they contain only the necessary runtime, with no shell or package manager:

FROM gcr.io/distroless/nodejs20-debian12
COPY --from=build /app /app
CMD ["/app/server.js"]

Strategy 3 — Update your application dependencies

When Trivy flags vulnerabilities in your node_modules, requirements.txt, or composer.json, the fix happens at the code level:

Terminal window
# Node.js
npm audit fix
# Python
pip install --upgrade vulnerable-package
# PHP
composer update vendor/vulnerable-package

Rebuild and rescan:

Terminal window
docker build -t my-app:latest .
trivy image my-app:latest --severity CRITICAL,HIGH

If the CRITICAL CVEs are gone, the fix is confirmed.

When no fix is available (Fixed Version is empty in the report), you can’t patch immediately. The right approach is to document the vulnerability in a SECURITY.md file, assess whether it’s actually exploitable in your specific context, and monitor the base image for updates.

Key Takeaway
Switching from node:18 to node:20-alpine in your Dockerfile is often enough to eliminate 80% of the CRITICAL CVEs Trivy identifies.

Step 5: Automate scanning with a GitHub Action or a Docker cron job

Running Trivy manually is a good start. Integrating it into a CI/CD pipeline is what actually protects your infrastructure over time.

GitHub Actions — complete workflow file:

.github/workflows/trivy-scan.yml
name: Trivy Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * 1' # Every Monday at 6am UTC
jobs:
trivy-scan:
name: Scan for vulnerabilities
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t my-app:${{ github.sha }} .
- name: Scan with Trivy
uses: aquasecurity/trivy-action@0.29.0
with:
image-ref: my-app:${{ github.sha }}
format: table
exit-code: '1' # Fails the build if CRITICAL is detected
severity: 'CRITICAL,HIGH'
ignore-unfixed: true # Ignores CVEs with no available fix

The key parameter is exit-code: '1': if Trivy detects a CRITICAL or HIGH vulnerability with an available fix, the build fails automatically. The code never reaches production.

No CI/CD? Use a cron job on the server:

On a Coolify server without a GitHub pipeline, set up a weekly cron job:

Terminal window
# Add to crontab -e
0 7 * * 1 /home/coolify/scan-all-images.sh 2>&1 | mail -s "Trivy Report $(date +%Y-%m-%d)" admin@your-domain.com

To send the report to Slack instead of email, replace the mail command with a curl call to an incoming Slack webhook.

Store reports to track progress over time:

The script from Step 3 already exports JSON files into timestamped folders (./trivy-reports/2026-02-25/). You can compare reports week over week to measure the reduction in CVE count — a concrete indicator of improving security posture.

Add ignore-unfixed: true to your automated scans to cut through the noise of CVEs with no available patch. Keep alerts focused on what’s actually actionable.

Conclusion: Docker security within reach of any SMB

This tutorial covered the 5 essential steps: installing Trivy, scanning an image, reading the report, fixing vulnerabilities, and automating the whole process.

The concrete result: a Docker infrastructure that’s more resilient to supply-chain attacks, with no dedicated security budget or advanced expertise required. Switching from node:18 to node:20-alpine and wiring up a GitHub Action is about an hour of work that meaningfully reduces your attack surface.

Trivy goes well beyond image scanning. In a future article, we’ll explore how to use it to analyze Dockerfile configurations and Kubernetes manifests, and how to pair it with Falco for real-time threat detection on running containers.

Need help securing your Docker infrastructure or setting up a robust CI/CD pipeline? At Kodixar, I help SMBs implement DevSecOps practices that fit their size and budget.

Available for new projects

Need help with this topic?

Contact us to discuss your project and see how we can help.

Free quote
No commitment
24h response