Container security is no longer optional. With supply chain attacks on the rise, securing container images from build to runtime is a fundamental requirement for any organization running containerized workloads. Runtime security alone is insufficient; supply chain security must start at the image build stage.
The Container Supply Chain Threat Landscape
Attack vectors in the container supply chain include compromised base images, vulnerable dependencies, leaked secrets, and malicious packages. Real-world incidents such as the Codecov breach exposing credentials, dependency confusion attacks, and cryptominers found in public images highlight the severity of these threats. The shared responsibility model means that while platforms like Docker provide base infrastructure, the security of your built images is your responsibility.
Image Vulnerability Scanning Tools
Several tools are available for scanning container images. Trivy by Aqua Security is open source, fast, and comprehensive, scanning both OS packages and language-specific dependencies across npm, pip, gem, and Go. Snyk offers a commercial scanner with a developer-friendly CLI, PR checks, and automated fix suggestions. Grype from Anchore integrates with Syft for SBOM generation, while Docker Scout is built directly into Docker Desktop and Docker Hub.
| Tool | Type | Key Strength |
|---|---|---|
| Trivy | Open source | Speed and breadth of coverage |
| Snyk | Commercial | Fix suggestions and PR integration |
| Grype | Open source | SBOM integration with Syft |
| Docker Scout | Built-in | Seamless Docker Desktop integration |
Dockerfile Best Practices for Security
Minimal base images are the foundation of secure containers. Prefer Alpine, distroless, or scratch over full OS images. Multi-stage builds separate build dependencies from runtime artifacts, allowing you to produce a final image containing only what is needed to run the application.
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o app .
FROM alpine:3.20
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder /app/app /app/app
USER appuser
ENTRYPOINT ["/app/app"]
Avoid using the latest tag; pin to specific digests for reproducible builds. Run containers as a non-root user, drop unnecessary Linux capabilities, and mount the root filesystem as read-only with a tmpfs for writable directories.
Secrets Management in Containers
Secrets must never be baked into images. Avoid using ENV or COPY with credentials in Dockerfiles. Instead, use Docker BuildKit’s –secret flag for build-time secrets and runtime secret stores such as Docker Secrets, Kubernetes Secrets, or external solutions like HashiCorp Vault and AWS Secrets Manager.
# Build with build-time secrets (no secret in image layers)
DOCKER_BUILDKIT=1 docker build \
--secret id=mysecret,src=./secret.txt \
-t myapp:latest .
A well-configured .dockerignore file prevents accidental inclusion of .env files, SSH keys, or other credentials in the build context.
Image Signing with Cosign
Image signing provides cryptographic verification that an image was produced by a trusted source. Cosign, part of the Sigstore project, supports keyless signing using OIDC providers such as GitHub, Google, and Microsoft. It generates ephemeral keys through the Fulcio certificate authority and records signatures in the Rekor transparency log.
# Keyless signing
cosign sign ghcr.io/myorg/myapp:latest
cosign verify ghcr.io/myorg/myapp:latest
Image attestations using in-toto metadata enable SLSA compliance, and policy enforcement tools like Kyverno and Connaisseur can require verified signatures before deploying images in Kubernetes clusters.
Runtime Security
Runtime security layers protect running containers. Seccomp profiles restrict the system calls available to containers, AppArmor or SELinux enforce mandatory access control, and read-only filesystems prevent runtime modifications. Resource limits on memory and CPU mitigate denial-of-service risks, and rootless mode runs the Docker daemon without root privileges. For advanced threat detection, Falco from the CNCF monitors container behavior for anomalous activity.
CI/CD Integration
Security scanning must be integrated at every stage of the pipeline. Scan on the developer workstation with trivy fs, in pull requests using action runners, at build time, in the registry, and before deployment. Policy as code defines vulnerability severity thresholds that determine whether a build passes or fails.
name: Container Security Scan
on: [pull_request]
jobs:
trivy-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
severity: CRITICAL,HIGH
exit-code: 1
SBOM generation with Syft creates SPDX or CycloneDX bills of materials that can be attached to releases for auditability and vendor compliance.
Minimal Base Images Comparison
| Image | Size | Shell | Package Manager | Best For |
|---|---|---|---|---|
| Alpine | 5 MB | Yes | apk | General purpose |
| Distroless | 12-20 MB | No | None | Production apps |
| Chainguard | 15-25 MB | No | apk (minimal) | Security-critical |
| Scratch | 0 MB | No | None | Static binaries |
Continuous Monitoring and Compliance
Registry scanning continuously monitors images in Docker Hub, ECR, GCR, and Harbor for new CVEs. Vulnerability database freshness is critical; Trivy updates its database hourly. CVE triage using the Exploit Prediction Scoring System helps distinguish exploitable vulnerabilities from theoretical ones. Compliance frameworks such as CIS Docker Benchmark, NIST SP 800-190, and SOC 2 provide structured guidelines. Automated remediation tools like Dependabot and Renovate create pull requests for patched base image versions.
Conclusion
A layered container security strategy combines build-time scanning, runtime monitoring, and policy enforcement. Start with Trivy in CI/CD, adopt minimal base images with multi-stage builds, implement image signing with Cosign for production registries, and continuously monitor vulnerability databases. Security is not a final step in the container lifecycle but an ongoing practice integrated into every phase of development and deployment.
