Container image security is one of the most overlooked risks in modern software delivery. Before a single container runs in production, the image it is built from may already carry known vulnerabilities, hardcoded secrets, bloated attack surface, or packages pulled from unverified sources. For Indian DevOps teams shipping containerised applications — on AWS, GCP, or bare metal — scanning Docker images in CI is not optional. It is the difference between shipping clean software and shipping a pre-compromised payload. This guide covers what goes wrong inside container images, how to scan them with Trivy, Grype, and Clair, and how to build a secure image pipeline that fits Indian team constraints.
Why Container Images Are a Security Minefield
The typical Docker image is not a clean slate. It inherits everything in the base image — and base images are often years out of date. A Node.js 16 Alpine image pulled from Docker Hub in 2025 ships with dozens of known CVEs. The developer who writes the FROM node:16-alpine line is unaware of this; the CI pipeline that builds and ships the image never checks.
NIST SP 800-190, the definitive US government guide on container security, identifies five primary image risk categories: vulnerable components in the image, malicious content introduced during the build, inclusion of unnecessary software, configuration weaknesses, and embedding sensitive data. Every one of these applies to Indian teams.
The Sysdig 2024 Cloud-Native Security and Usage Report found that 87% of container images running in production had high or critical vulnerabilities. The problem is upstream: developers trust base images, base image maintainers update on their own schedule, and registries serve stale layers silently.
The risks group into five distinct categories, each requiring a different control.
Vulnerable Base Images
The FROM line is the first attack surface. Debian Buster, Ubuntu 20.04, and older Node or Python official images carry packages with dozens of known CVEs. Attackers actively scan for containers running these versions. Selecting a minimal or distroless base image eliminates the majority of this surface before any code is written.
Outdated Application Packages
Dependencies installed via apt, pip, npm, or yarn during the build inherit whatever vulnerabilities exist in their ecosystem at build time. A package-lock.json locked to a specific version months ago may include packages with high-severity CVEs published since the lock. Scanning at build time catches these before they reach registry.
Hardcoded Secrets and Credentials
Build pipelines frequently leak secrets into image layers. A RUN aws configure line, a .env file copied before a subsequent RUN rm, or an SSH key added during build — all of these persist in intermediate layers even if deleted in a later step. The image layer is immutable and the secret is recoverable with docker history. NIST SP 800-190 Section 4.1.5 explicitly flags this as a critical image risk.
Supply-Chain and Base Image Tampering
Pulling FROM ubuntu:latest from Docker Hub means trusting Docker Hub. Images with typosquatted names, compromised accounts, or injected malware have appeared in public registries. Supply-chain attacks on container base images are increasing. Signing and verifying images using Cosign and Sigstore is the only reliable defence.
Bloated Images with Unnecessary Tooling
Images built with full build toolchains — gcc, curl, git, bash — expand the attack surface drastically. An attacker who achieves RCE in a container with curl installed can trivially exfiltrate data or download next-stage payloads. A distroless image or a scratch-based binary has no shell, no package manager, and no network utilities for an attacker to use.
The Container Image Vulnerability Pipeline
graph TD
A[Dev writes Dockerfile] --> B[Multi-stage build]
B --> C[Minimal base image selected]
C --> D[Image built in CI]
D --> E[Trivy or Grype scan]
E --> F{Vulnerabilities found?}
F -->|CRITICAL or HIGH| G[Block merge - fix required]
F -->|LOW or MEDIUM| H[Log and continue]
G --> I[Dev patches base image or dependency]
I --> D
H --> J[Sign image with Cosign]
J --> K[Push to private registry]
K --> L[Registry admission check]
L --> M[Deploy to production]
style A fill:#1e3a5f,stroke:#3B82F6,color:#e2e8f0
style B fill:#1e3a5f,stroke:#3B82F6,color:#e2e8f0
style C fill:#1e3d2f,stroke:#10B981,color:#e2e8f0
style D fill:#1e3a5f,stroke:#3B82F6,color:#e2e8f0
style E fill:#1e3a5f,stroke:#3B82F6,color:#e2e8f0
style F fill:#1e3a5f,stroke:#3B82F6,color:#e2e8f0
style G fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0
style H fill:#1e3d2f,stroke:#10B981,color:#e2e8f0
style I fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0
style J fill:#1e3d2f,stroke:#10B981,color:#e2e8f0
style K fill:#1e3d2f,stroke:#10B981,color:#e2e8f0
style L fill:#1e3a5f,stroke:#3B82F6,color:#e2e8f0
style M fill:#1e3d2f,stroke:#10B981,color:#e2e8f0Sources of Vulnerabilities in Container Images
pie title Sources of Container Image Vulnerabilities (illustrative)
"Vulnerable OS packages in base image" : 38
"Outdated app dependencies" : 27
"Hardcoded secrets in layers" : 15
"Unnecessary tooling and bloat" : 12
"Supply-chain and registry tampering" : 8Know your vulnerabilities before attackers do
Run a free VAPT scan — takes 5 minutes, no signup required.
Book Your Free ScanScanning Tools: Trivy, Grype, and Clair
Three open-source tools dominate container image scanning. Each has different strengths and CI integration patterns.
| Tool | Strengths | Best For | CI Integration |
|---|---|---|---|
| Trivy (Aqua Security) | Fastest scan, scans OS + app layers + secrets, SARIF output | Most teams — lowest friction | GitHub Actions, GitLab CI, Docker build step |
| Grype (Anchore) | Deep SBOM generation with Syft, accurate CVE matching | Teams needing Software Bill of Materials | Jenkins, GitLab CI |
| Clair (Red Hat/Quay) | Deep integration with Quay registry, REST API | Enterprise registry-native scanning | Quay.io, Kubernetes admission |
image-scan:
stage: test
image: aquasec/trivy:latest
script:
- trivy image --exit-code 1 --severity CRITICAL,HIGH $IMAGE_TAGThe --exit-code 1 flag causes the pipeline to fail on any CRITICAL or HIGH finding. This is the most important configuration choice: scanning without blocking is auditing theatre.
Minimal and Distroless Base Images
The single highest-impact change most teams can make is replacing a general-purpose base image with a minimal or distroless one. Google's distroless images contain only the application runtime and its direct dependencies — no shell, no package manager, no debug tools.
Before:
FROM node:18After:
FROM gcr.io/distroless/nodejs18-debian12The distroless image eliminates an entire class of OS-package vulnerabilities and removes attacker tooling from the runtime. Combined with a multi-stage build, it also dramatically reduces image size.
A multi-stage build separates the build environment (which needs compilers, dev dependencies, and tools) from the runtime image (which needs only the compiled output). This prevents build secrets, dev certs, and unnecessary binaries from reaching production.
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM gcr.io/distroless/nodejs18-debian12 AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["dist/index.js"].env files, SSH keys, or AWS credentials into a Docker image, even temporarily. Use build secrets (--secret flag in BuildKit), environment variables injected at runtime, or a secrets manager. Anything copied into a layer — then deleted — is still recoverable from image history.Image Signing and Provenance
Signing container images gives downstream consumers — your CI runner, your Kubernetes admission controller, your registry — cryptographic proof that a specific image was built by your pipeline and has not been tampered with.
The current standard is Cosign from the Sigstore project, backed by Google, Red Hat, and the Linux Foundation. Cosign signs images using OIDC tokens from your CI provider (GitHub Actions, GitLab CI) with no key management required in keyless mode.
A CI signing step:
cosign sign --yes $REGISTRY/$IMAGE@$DIGESTVerification before deploy:
cosign verify --certificate-identity-regexp=".*" \
--certificate-oidc-issuer="https://accounts.google.com" \
$REGISTRY/$IMAGE@$DIGESTFor Indian teams on GitLab self-hosted or AWS CodePipeline, Cosign's keyless flow works with any OIDC-compatible CI provider. The signature is stored in the registry alongside the image, requiring no separate signature storage.
Registry Security
Pushing images to Docker Hub public repositories is a security choice, not a default. Public repositories expose your image layers, your dependency tree, and potentially embedded secrets to anyone with a pull command.
Practical controls for Indian teams:
- Use a private registry: AWS ECR, Google Artifact Registry, GitLab Container Registry, or Harbor self-hosted
- Enable immutable image tags — prevent tag overwrites that could allow a compromised image to be silently substituted
- Enable lifecycle policies to delete unscanned or stale images
- Require vulnerability scanning before images are made available for deployment (AWS ECR Enhanced Scanning, GCR Artifact Analysis)
- Restrict registry pull access to specific service accounts and CI runners — not developer laptops
Building a Secure Image Pipeline Checklist
| Control | Tool / Method | Priority |
|---|---|---|
| Scan image for OS vulnerabilities | Trivy, Grype, Clair | P0 — block on CRITICAL/HIGH |
| Scan for hardcoded secrets | Trivy secret scan, GitLeaks | P0 — block on any finding |
| Use minimal or distroless base image | Google distroless, Alpine, scratch | P0 |
| Multi-stage builds to separate build and runtime | Dockerfile best practice | P0 |
| Sign images and verify before deploy | Cosign / Sigstore | P1 |
| Use private registry with immutable tags | ECR, GAR, GitLab, Harbor | P1 |
| SBOM generation for audit trail | Syft + Grype | P1 |
| Scheduled re-scan of registry images | ECR Enhanced Scan, Trivy cron | P1 |
| Restrict registry pull to CI/CD only | IAM policies, registry access controls | P2 |
| Lock base image digests not tags | FROM ubuntu@sha256:... | P2 |
Scanning in CI: Making It Stick
The most common failure mode is installing a scanner but not blocking on findings. Teams add Trivy to their pipeline, see a wall of CVEs, suppress the exit code so the pipeline does not break, and return to the same state they were in before.
An effective rollout has three phases:
Phase 1 — Audit mode (week 1): Scan all images, log findings, set --exit-code 0. Build a baseline of what your current images look like. This surfaces the scale of the problem without blocking anything.
Phase 2 — Block on CRITICAL (week 2–3): Set --exit-code 1 --severity CRITICAL. Fix or accept-risk the CRITICAL findings. Most CRITICAL findings resolve by updating the base image.
Phase 3 — Block on HIGH (week 4+): Extend the block to HIGH severity. This is where dependency updates become necessary. Most teams reach steady state within a sprint.
Bachao.AI's automated VAPT platform includes external scanning of containerised web services as part of its scanning suite. Dhisattva AI Pvt Ltd built the platform to give Indian teams a starting point for validating their external attack surface — a free VAPT scan covers web-facing endpoints and helps identify what is reachable from the outside before container hardening fixes reach it internally.
FROM line, not at runtime. Switch to distroless or minimal base images, scan with Trivy on every build and fail the pipeline on CRITICAL/HIGH findings, strip secrets from layers using BuildKit secrets, and sign images before pushing to your private registry. These four controls eliminate the majority of image-layer risk before a single pod starts.What Indian DevOps Teams Get Wrong
Most Indian startup and mid-market teams treat container security as an infrastructure concern and leave it to the platform team. But the platform team does not write the Dockerfiles — developers do. Closing this gap requires two things: shifting scanning left into the developer workflow (pre-commit hooks, IDE plugins, CI gates) and making the default image choice the secure one (distroless, pinned digest, private registry).
CERT-In's advisories have increasingly flagged container and cloud-native vulnerabilities as the vector in incidents affecting Indian IT companies. The attack path is consistent: public-facing containerised service, vulnerable OS package in base image, known CVE exploited, lateral movement inside the cluster. The image scan that would have caught the CVE was either missing or set to non-blocking mode.
Check the Bachao.AI blog for related guides on Kubernetes cluster hardening, secrets management in cloud-native environments, and CI/CD pipeline security.
Frequently Asked Questions
What is the fastest way to start scanning Docker images in an existing CI pipeline?
aquasec/trivy:latest Docker image in your pipeline. Add a scan step after your image build step with trivy image --exit-code 1 --severity CRITICAL,HIGH <your-image-tag>. This takes under 10 minutes to set up and immediately starts blocking on critical vulnerabilities.Should I scan base images or only my final application image?
What is a distroless image and does it work for all applications?
How do I prevent secrets from being embedded in Docker image layers?
--secret flag to inject secrets during build without persisting them in the layer. Never use COPY .env . or ARG AWS_SECRET_KEY — ARG values appear in docker history output and are recoverable. Use a secrets manager (AWS Secrets Manager, HashiCorp Vault) and inject values at runtime via environment variables, not at build time.