
September 11, 2026
11 min read
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.
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.
| Scanner | Best for | SBOM output | CI integration | License |
|---|---|---|---|---|
| Trivy | All-in-one CI scans | Yes (CycloneDX, SPDX) | Excellent | Apache 2.0 |
| Grype + Syft | SBOM-first workflows | Yes (native) | Excellent | Apache 2.0 |
| Docker Scout | Docker Hub users | Limited | Good | Commercial tiers |
| Harbor Trivy plug-in | Private registry gate | Via Trivy | At push time | Apache 2.0 |
| GitLab Container Scanning | GitLab CI native jobs | Yes | Built-in template | GitLab 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.
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.
- Build the image in CI with a pinned Dockerfile and base image digest.
- Run the scanner with
--exit-code 1on CRITICAL and HIGH findings. - Export an SBOM artifact for compliance archives.
- Push to the registry only after the scan job passes.
- Re-scan on a nightly schedule because new CVEs appear daily.
- 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.
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-devin 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.
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
.trivyignorewith 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
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.

