Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Scan Container Images for Vulnerabilities

By Kokil Thapa | Last reviewed: September 2026

You deploy a Laravel app inside a Docker image and assume production is safe because the app code passed review. Then a scanner flags CVE-2024-XXXX in OpenSSL inside the base image you pulled six months ago. To scan container images for vulnerabilities means checking every layer — OS packages, language libraries, and sometimes secrets — before that image reaches your registry or Kubernetes cluster. On real client projects I maintain with GitLab CI and Deployer pipelines, image scanning sits between build and deploy. It catches problems that unit tests never will.

Why should you scan container images for vulnerabilities before production?

Container images are frozen filesystems. They bundle your PHP runtime, nginx, system libraries, and Composer vendor trees into immutable layers. A vulnerability in any layer affects every pod that runs that tag.

Application tests verify business logic. They do not inspect glibc, libxml2, or an outdated Node.js binary left in a multi-stage build. Image scanning closes that gap.

The attack surface grows fast on typical stacks. A Laravel 13 image built from php:8.3-fpm may carry hundreds of Debian packages. Add Redis client libraries, imagemagick, or a debugging tool you forgot to strip from production, and your CVE count climbs.

Regulators and enterprise clients increasingly ask for SBOMs and scan reports. Even small teams in Nepal shipping booking portals or eCommerce stores benefit from a documented scan step. It costs minutes in CI and saves days of incident response.

Scan Container Images for VulnerabilitiesDocker BuildDockerfile layersCVE ScanTrivy / GrypePolicy GateFail on CRITICALRegistryTagged imageWhat each layer exposesOS packagesApp depsSecretsScanners match installed packages against CVE databases like NVDOnly patched images proceed to production deploy
Container image vulnerability scanning pipeline — build, scan, enforce policy, then push to registry

Scanning also pairs well with other hardening steps. Combine it with distroless or minimal base images, image signing with Cosign, and rootless runtime options. Defense works best in layers, not as a single checkbox.

Which tools should you use to scan container images for vulnerabilities?

Several mature scanners read OCI and Docker image tarballs without running the container. They download vulnerability databases daily and map installed packages to known CVEs. Pick one primary tool for CI consistency. Add a registry-native scanner if your platform includes it.

Trivy

Trivy from Aqua Security is the default choice for many teams. It scans OS packages, language dependencies, IaC misconfigurations, and secrets in one binary. Install via package manager or run the official container image. It fits GitLab CI, GitHub Actions, and local developer workflows.

Grype and Syft

Grype from Anchore scans images for CVEs. Syft generates SBOMs from the same image. Use both when compliance asks for a software bill of materials alongside the vulnerability report.

Docker Scout and registry scanners

Docker Scout integrates with Docker Hub and Desktop. GitLab, Harbor, Amazon ECR, and Google Artifact Registry offer built-in or optional scanning. These help for images already stored in a registry. CI-stage scanning still matters because it blocks bad images before push.

ScannerBest forSBOM outputCI integrationLicense
TrivyAll-in-one CI scansYes (CycloneDX, SPDX)ExcellentApache 2.0
Grype + SyftSBOM-first workflowsYes (native)ExcellentApache 2.0
Docker ScoutDocker Hub usersLimitedGoodCommercial tiers
Harbor Trivy plug-inPrivate registry gateVia TrivyAt push timeApache 2.0
GitLab Container ScanningGitLab CI native jobsYesBuilt-in templateGitLab tier dependent

For a deeper tool walkthrough, see the dedicated guides on Trivy for containers and IaC and container image scanning with Trivy. If you run a private registry, Harbor with Trivy integration adds scan-on-push enforcement.

What Scanners InspectContainer Image LayersLayer 1: Base OS (Debian/Alpine)Layer 2: PHP 8.3 + extensionsLayer 3: Composer vendor/Layer 4: App code + configsOS package CVEsapt / apk databasesLanguage depsComposer, npm, pipSecret detectionAPI keys in layersMisconfigurationsDockerfile best practice
Container vulnerability scanners inspect OS packages, language dependencies, secrets, and Dockerfile misconfigurations across image layers

How do you scan container images for vulnerabilities in CI/CD?

Scan after docker build and before docker push. That order ensures every tag entering your registry passed policy. On sister sites I deploy with Deployer 7 and GitLab CI, the scan job runs on the same runner that built the image.

Local scan with Trivy

Install Trivy on Ubuntu 24.04 or run it from the official image. Build your image first, then scan:

docker build -t myapp:1.4.2 .
trivy image --severity CRITICAL,HIGH --exit-code 1 myapp:1.4.2

The --exit-code 1 flag fails the command when matching severities appear. That makes it CI-friendly. Add --ignorefile .trivyignore for accepted risks with ticket references.

GitLab CI example

GitLab ships a Container Scanning template. A minimal custom job using the Trivy image looks like this:

stages:
  - build
  - scan
  - deploy

build_image:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

container_scan:
  stage: scan
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  script:
    - trivy image
        --severity CRITICAL,HIGH
        --exit-code 1
        --format table
        $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  needs: ["build_image"]

Validate YAML structure with a JSON or config formatter before pushing pipeline changes. Broken indentation silently skips jobs.

Scan tarballs without a local daemon

CI runners without Docker socket access can scan saved tarballs:

docker save myapp:1.4.2 -o image.tar
trivy image --input image.tar --severity CRITICAL,HIGH --exit-code 1

This pattern works in rootless or Kaniko-based builds where the daemon never exposes images by tag.

  1. Build the image in CI with a pinned Dockerfile and base image digest.
  2. Run the scanner with --exit-code 1 on CRITICAL and HIGH findings.
  3. Export an SBOM artifact for compliance archives.
  4. Push to the registry only after the scan job passes.
  5. Re-scan on a nightly schedule because new CVEs appear daily.
  6. Block deploy jobs unless the scan artifact exists for that commit SHA.

Pair container scans with application dependency scanning for Composer and npm. A clean composer audit does not guarantee a clean OS layer inside the image.

CI/CD Scan Workflowgit push triggers pipelineBuild Imagedocker buildTrivy ScanCVE policy gateComposer AuditPHP app depsDeploy Gate: both jobs greenDeployer 7 release to productionAny CRITICAL CVE blocks deploy automatically
CI/CD workflow — parallel container image and dependency scans must pass before Deployer release

For registry choice context, read the Docker Hub vs GitLab vs ECR comparison. Scanning policy should follow the registry you actually use in production.

How do you interpret CVE severity and fix container image vulnerabilities?

A scan report lists CVE IDs, affected packages, installed versions, fixed versions, and CVSS scores. The NIST National Vulnerability Database holds authoritative details. Start with CRITICAL and HIGH items that have a fixed version available.

Fix by rebuilding, not patching inside running containers

Immutable infrastructure means you fix the Dockerfile or base image tag, rebuild, and redeploy. Never run apt upgrade inside a running production container and call it done. The old vulnerable layer still exists in the registry tag.

Common fixes for PHP/Laravel stacks:

  • Pin base images by digest and bump weekly: php:8.3-fpm-bookworm@sha256:…
  • Use multi-stage builds to drop build tools from the final layer
  • Run composer install --no-dev in production stages
  • Remove curl, git, and shell utilities from runtime images when possible
  • Switch to slim or alpine variants only after testing extension compatibility

When to ignore a finding

Not every CVE is exploitable in your workload. A kernel CVE may not apply if you run on a managed Fargate or Cloud Run substrate. Document accepted risks in .trivyignore with a Jira or GitLab issue ID and expiry date.

# .trivyignore — example with expiry comment
CVE-2023-12345 exp:2026-12-31
# Reason: not reachable; service binds localhost only; tracked in PROJ-412

Automate reassessment with vulnerability management automation. Ignored CVEs should resurface when the expiry date passes.

Vulnerability Fix CycleScan failsCRITICAL CVEUpdate baseBump digestRebuilddocker buildPassRe-scan OKCommon gotchas that keep CVEs aliveCached old layers in CIFloating :latest tagsDev tools in prod stageScan only on main branch
Remediation cycle — update base image digest, rebuild, re-scan, and avoid common gotchas that leave CVEs in production tags

IaC that provisions your cluster deserves the same treatment. Run Checkov on Terraform or the broader IaC security scan stack alongside image scans. A patched image on a misconfigured cluster still creates risk.

What common mistakes break container vulnerability scanning programs?

Teams adopt scanning, then wonder why production still carries known CVEs. These patterns show up repeatedly on production systems I help maintain.

Scanning only on release branches

Developers merge dependency and Dockerfile changes on feature branches. If scan jobs run only on main, bad layers merge before anyone sees the report. Run scans on every merge request that touches a Dockerfile or lock file.

Using floating tags in production

Pulling php:8.3-fpm without a digest means today's build differs from last week's. Pin digests in CI and record them in deploy logs. See Docker image tagging strategies for semver and digest patterns that work.

Treating scan success as permanent

CVE databases update daily. An image that passed last month may fail today. Schedule nightly registry rescans. Harbor and GitLab both support recurring scan policies on stored tags.

Ignoring the application layer

Container scans complement but do not replace Composer, npm, or pip audits. Use both. For Laravel apps on PHP 8.3 or 8.5, keep framework versions current — Laravel 12 is supported to February 2027, and Laravel 11 reached EOL in March 2026.

Skipping scan artifacts

Store SARIF or JSON reports as CI artifacts for thirty to ninety days. Auditors ask for proof. A green pipeline badge is not enough evidence.

Projects like Adventure Third Pole Trek and Notary Kathmandu run on the same GitLab plus Deployer workflow I use for other production sites. Adding a scan stage there took one pipeline job and prevented a risky base image from reaching EC2. The effort was small relative to the downside.

If your team lacks CI capacity, testing and optimization services or enterprise application development can wire scanning into an existing Laravel or Symfony delivery pipeline without a full platform rewrite.

Key Takeaways

  • Scan container images for vulnerabilities after every build and before registry push, failing CI on CRITICAL and HIGH CVEs with --exit-code 1.
  • Trivy is the practical default for CI; pair Grype and Syft when you need SBOM deliverables for compliance.
  • Fix findings by rebuilding with updated base image digests — never patch running containers in place.
  • Document accepted CVEs in .trivyignore with ticket IDs and expiry dates, then automate nightly rescans.
  • Combine image scanning with dependency audits, IaC checks, minimal base images, and image signing for defense in depth.
  • Pin image digests, scan merge requests, and archive SARIF reports so audits have evidence beyond a green pipeline icon.

People Also Ask

How often should you scan container images for vulnerabilities?

Scan on every build in CI/CD at minimum. Also schedule nightly or weekly rescans of tags stored in your registry. New CVEs publish between builds, and a passing image yesterday may fail today without any code change on your side.

Can Trivy scan running containers or only images?

Trivy primarily scans image tarballs, filesystems, and repositories. For running workloads, scan the image digest that the container was started from. That matches what you deployed and what belongs in the registry of record.

What severity threshold should fail a CI pipeline?

Most teams fail on CRITICAL and often HIGH severities. MEDIUM may warn but not block initially. Tune thresholds per environment: block staging and production equally once your base images are reasonably current.

Do smaller Alpine-based images always have fewer vulnerabilities?

Not always. Alpine uses musl and different package names. Fewer packages can mean fewer CVEs, but compatibility issues with PHP extensions cause teams to add packages back. Measure with scans instead of assuming smaller equals safer.

Ship safer containers with scanning built into delivery

Scan container images for vulnerabilities as a hard gate in your pipeline, not as an optional report you read once a quarter. Build with pinned digests, scan with Trivy or Grype, fix by rebuilding, and re-scan before deploy. That workflow fits the GitLab CI pipelines I run for production Laravel and WordPress systems today.

Need help wiring scan jobs into an existing Deployer or GitLab setup? Contact us for a practical review. Explore more on the blog, browse portfolio projects with live CI pipelines, or read about ongoing support and maintenance for production containers.

Frequently Asked Questions

It means checking every layer of a built image—OS packages, language libraries, and sometimes secrets—before the image reaches your registry or cluster, using a scanner that maps installed software to known CVEs.

Container images are frozen filesystems bundling your PHP runtime, nginx, system libraries, and Composer vendor trees into immutable layers. Application tests verify business logic; they do not inspect glibc, libxml2, or an outdated Node.js binary left in a multi-stage build. A vulnerability in any layer affects every pod running that tag. On GitLab CI and Deployer pipelines I maintain, image scanning sits between build and deploy and catches problems unit tests never will. Regulators and enterprise clients increasingly ask for SBOMs and scan reports. Even small teams shipping booking portals or eCommerce stores benefit from a documented scan step that costs minutes in CI and saves days of incident response.

Pick one primary scanner for CI consistency and add a registry-native option if your platform includes it. Trivy from Aqua Security is the practical default—it scans OS packages, language dependencies, IaC misconfigurations, and secrets in one binary and integrates well with GitLab CI and GitHub Actions. Grype scans CVEs and Syft generates SBOMs when compliance needs a software bill of materials alongside the report. Docker Scout suits Docker Hub users. Harbor with Trivy integration enforces scan-on-push on private registries. GitLab Container Scanning provides built-in template jobs. CI-stage scanning still matters because it blocks bad images before push, regardless of registry-side scanning.

Scan after docker build and before docker push so every tag entering your registry passed policy. On sister sites I deploy with Deployer 7 and GitLab CI, the scan job runs on the same runner that built the image. Build the image, then run Trivy with severity CRITICAL,HIGH and exit-code 1 so matching findings fail the pipeline. Add a .trivyignore file for accepted risks with ticket references. Runners without Docker socket access can docker save the image to a tarball and scan with trivy image --input. Export an SBOM artifact for compliance, push only after the scan job passes, and block deploy jobs unless a scan artifact exists for that commit SHA. Pair container scans with Composer and npm audits because a clean application layer does not guarantee a clean OS layer.

Scan on every build in CI/CD at minimum. Also schedule nightly or weekly rescans of tags stored in your registry, because new CVEs publish between builds and a passing image yesterday may fail today without any code change.

Most teams fail on CRITICAL and often HIGH. MEDIUM may warn but not block initially. Tune thresholds per environment and block staging and production equally once base images are reasonably current.

Trivy primarily scans image tarballs, filesystems, and repositories. For running workloads, scan the image digest the container was started from—that matches what you deployed and what belongs in the registry of record.

A scan report lists CVE IDs, affected packages, installed versions, fixed versions, and CVSS scores. Check the NIST National Vulnerability Database for authoritative details. Start with CRITICAL and HIGH items that have a fixed version available. Fix by rebuilding, not patching inside running containers—update the Dockerfile or base image digest, rebuild, and redeploy. Never run apt upgrade inside a running production container; the old vulnerable layer still exists in the registry tag. For PHP/Laravel stacks, pin base images by digest, use multi-stage builds to drop build tools, run composer install --no-dev in production stages, and remove curl, git, and shell utilities from runtime images when possible. Switch to slim or alpine variants only after testing extension compatibility.

Not always. Alpine uses musl and different package names. Fewer packages can mean fewer CVEs, but compatibility issues with PHP extensions cause teams to add packages back, which changes the attack surface. Measure with scans instead of assuming smaller equals safer. On real Laravel images built from php:8.3-fpm, the CVE count depends on what you install in the Dockerfile, not only the base variant. Run Trivy or Grype against both slim and alpine candidates before committing to either. The goal is fewer exploitable findings in your actual workload, not the smallest image name on Docker Hub.

Both read OCI and Docker image tarballs without running the container and map installed packages to known CVEs. Trivy is the all-in-one choice for CI scans—it covers OS packages, language dependencies, IaC misconfigurations, and secrets, outputs SBOMs in CycloneDX and SPDX formats, and integrates cleanly with GitLab CI via the official aquasec/trivy image. Grype from Anchore focuses on CVE scanning and pairs naturally with Syft for SBOM-first compliance workflows where the bill of materials is the primary deliverable. Use Trivy as the default pipeline gate; add Grype and Syft when auditors explicitly require native SBOM generation alongside vulnerability reports.

Scanning only on release branches lets bad Dockerfile changes merge before anyone sees the report—run scans on every merge request that touches a Dockerfile or lock file. Floating tags like php:8.3-fpm without a digest mean today's build differs from last week's; pin digests in CI. Treating scan success as permanent fails when CVE databases update daily; schedule nightly registry rescans via Harbor or GitLab policies. Ignoring the application layer leaves Composer or npm vulnerabilities untouched—use both image and dependency audits. Skipping scan artifacts means auditors have no proof; store SARIF or JSON reports for thirty to ninety days. I've seen these patterns repeatedly on production systems I help maintain.

Rebuild, never patch in place. Immutable infrastructure means you fix the Dockerfile or base image tag, rebuild the image, re-scan with Trivy using exit-code 1 on CRITICAL and HIGH findings, then redeploy through your normal GitLab CI and Deployer pipeline. Running apt upgrade inside a running production container does not remove the vulnerable layer from the registry tag—every new pod pulled from that tag still carries the old filesystem. Bump the pinned base image digest, rebuild with multi-stage Dockerfile changes if needed, push only after the scan job passes, and record the new digest in deploy logs. That cycle is the only remediation approach that keeps registry history trustworthy.

Not every CVE is exploitable in your workload. A kernel CVE may not apply on managed Fargate or Cloud Run substrate where your container shares the host kernel differently. When you accept the risk, document it in .trivyignore with a Jira or GitLab issue ID, a reason, and an expiry date so the finding resurfaces for reassessment. Example format: CVE-2023-12345 with exp:2026-12-31 and a comment explaining why the service is not reachable. Automate reassessment through vulnerability management so expired ignores fail the pipeline again. Ignoring should be rare, ticket-tracked, and time-bound—not a permanent way to keep a green build badge.

No. Container scans complement application dependency scanning; they do not replace it. A clean composer audit does not guarantee a clean OS layer inside the image, and a passing Trivy scan does not prove your Laravel vendor tree is free of known package CVEs. Run both in parallel before Deployer release. For Laravel apps on PHP 8.3 or 8.5, keep framework versions current—Laravel 12 is supported to February 2027, and Laravel 11 reached EOL in March 2026. The CI/CD workflow should require container image and dependency scans to pass together. Defense works best in layers: image scanning, Composer and npm audits, IaC checks with Checkov, minimal base images, and image signing with Cosign.

An SBOM is a software bill of materials—a structured inventory of every package and library inside a container image. Compliance and enterprise clients increasingly request SBOMs alongside vulnerability scan reports to prove what shipped in a given tag. Trivy exports SBOMs in CycloneDX and SPDX formats. Grype pairs with Syft, which generates SBOMs natively from the same image you scan for CVEs. Export the SBOM as a CI artifact and archive it with SARIF or JSON scan reports for thirty to ninety days. Auditors ask for proof beyond a green pipeline badge; the SBOM plus scan output documents exactly what was in the image at build time and which CVEs were evaluated against that inventory.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: