Skip to main content

05 - Container Security - Image Scanning and Runtime Protection

Container image vulnerabilities, scanning (Trivy, Clair), image signing, registry security, runtime monitoring (Falco), network policies, secrets in containers.

What do you think about this article?

At a Glance

Container image vulnerabilities, scanning (Trivy, Clair), image signing, registry security, runtime monitoring (Falco), network policies, secrets in containers.

Container Security: Why It's Fundamental

Containers have revolutionized how we distribute software, but they have also introduced new attack surfaces. A Docker image contains not only the application code, but also the base operating system, system libraries, and runtime dependencies. Each component can contain vulnerabilities that are inherited by the application.

Container security covers the entire lifecycle: from image building (build-time), to distribution in the registry (distribution), to production execution (runtime). In this article, we will explore tools and practices to protect every phase.

What You'll Learn

  • How container image scanning works with Trivy and Grype
  • Building secure images with multi-stage builds and distroless
  • Image signing and verification with Cosign
  • Runtime protection with Falco and network policies
  • Security context and Pod Security Standards in Kubernetes
  • Best practices for secure Dockerfiles

Trivy: Complete Vulnerability Scanner

Trivy by Aqua Security is the most popular container scanner, capable of analyzing Docker images, filesystems, Git repositories, and IaC configurations. It's fast, accurate, and easy to integrate into CI/CD pipelines.

Docker Image Scanning


# Install Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh

# Scan a Docker image
trivy image node:18-alpine

# Scan with severity filter
trivy image --severity CRITICAL,HIGH node:18-alpine

# Output in SARIF format for GitHub
trivy image --format sarif --output trivy-results.sarif myapp:latest

# Scan local filesystem
trivy fs --security-checks vuln,config .

# Scan with exit code for CI (fails if CRITICAL found)
trivy image --exit-code 1 --severity CRITICAL myapp:latest
      

Trivy Integration in GitHub Actions


# .github/workflows/container-security.yml
name: Container Security Scan
on:
  push:
    branches: [main]
  pull_request:

jobs:
  trivy-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build Docker image
        run: docker build -t myapp:${{ github.sha }} .

      - name: Trivy vulnerability scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: "myapp:${{ github.sha }}"
          format: "sarif"
          output: "trivy-results.sarif"
          severity: "CRITICAL,HIGH"
          exit-code: "1"

      - name: Upload SARIF to GitHub Security
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: "trivy-results.sarif"
      

Grype: Lightweight Alternative for Image Scanning

Grype by Anchore is a lightweight alternative to Trivy, focused exclusively on container image vulnerability scanning. It's particularly fast and integrates well with Syft (from the same team) for SBOM generation.


# Install Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh

# Scan an image
grype myapp:latest

# Scan with filter and JSON output
grype myapp:latest --only-fixed --output json > grype-results.json

# Scan with fail-on severity
grype myapp:latest --fail-on critical

# Generate SBOM with Syft and analyze with Grype
syft myapp:latest -o cyclonedx-json > sbom.json
grype sbom:sbom.json
      

Building Secure Docker Images

Container security starts with the Dockerfile. A well-built image drastically reduces the attack surface and number of vulnerabilities.

Multi-Stage Build

The multi-stage build pattern separates the compilation environment from the final image, eliminating build tools, sources, and development dependencies from the production image:


# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# Stage 2: Production (minimal image)
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
USER nonroot
CMD ["dist/server.js"]
      

Distroless Images

Google's distroless images contain only the application and its runtime dependencies, without shell, package manager, or other system tools. This reduces the attack surface to the absolute minimum.

Size and Vulnerability Comparison

Base Image Size Typical CVEs
node:20 ~1.1 GB 200-400
node:20-slim ~250 MB 50-100
node:20-alpine ~180 MB 10-30
distroless/nodejs20 ~130 MB 0-5

Dockerfile Security Best Practices

A secure Dockerfile follows specific rules that reduce the attack surface:


# 1. Use specific versions, never :latest
FROM node:20.11.0-alpine3.19

# 2. Create a non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# 3. Set WORKDIR
WORKDIR /app

# 4. Copy dependency files first (cache layer)
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

# 5. Copy application code
COPY --chown=appuser:appgroup . .

# 6. Don't expose privileged ports
EXPOSE 8080

# 7. Use non-root user
USER appuser

# 8. Use HEALTHCHECK
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1

# 9. Use exec form for CMD
CMD ["node", "dist/server.js"]
      

Image Signing with Cosign

Cosign (part of the Sigstore project) allows digitally signing container images, ensuring the image hasn't been modified after the build. This is fundamental for supply chain security.


# Install Cosign
brew install cosign  # macOS
# or
go install github.com/sigstore/cosign/v2/cmd/cosign@latest

# Generate a key pair
cosign generate-key-pair

# Sign an image
cosign sign --key cosign.key myregistry.com/myapp:v1.0.0

# Verify the signature
cosign verify --key cosign.pub myregistry.com/myapp:v1.0.0

# Sign with keyless (Sigstore/Fulcio)
cosign sign myregistry.com/myapp:v1.0.0
      

Runtime Protection with Falco

Falco by Sysdig is the reference open source tool for container runtime security. It monitors kernel syscalls in real-time and generates alerts when it detects anomalous or suspicious behavior.

What Falco Detects

  • Shell spawn inside a container
  • Access to sensitive files (/etc/shadow, /etc/passwd)
  • Unexpected network connections
  • System file modifications
  • Privilege escalation
  • Cryptomining activity

Kubernetes Pod Security

In Kubernetes environments, container security extends to Pod configuration. Pod Security Standards define three security levels:


# Pod with restrictive security context
apiVersion: v1
kind: Pod
metadata:
  name: secure-app
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: myapp:v1.0.0
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
      resources:
        limits:
          memory: "256Mi"
          cpu: "500m"
        requests:
          memory: "128Mi"
          cpu: "250m"
      

Network Policies

Network Policies in Kubernetes limit network traffic between Pods, implementing the principle of least privilege at the network level:


# Deny all ingress by default
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Ingress

---
# Allow only specific traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-api
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080
      

Container Security Best Practices

  1. Scan on every build: integrate Trivy/Grype in the CI pipeline for every image
  2. Minimal images: use Alpine or distroless as base, avoid full images
  3. Non-root: always run containers as non-root user
  4. Read-only filesystem: mount root filesystem as read-only
  5. No latest tag: always use specific tags with SHA256 digest
  6. Private registry: use a private registry with integrated scanning
  7. Image signing: sign every image with Cosign before deploying
  8. Runtime monitoring: implement Falco for production monitoring

Conclusions

Container security is a critical aspect of DevSecOps that requires attention at every stage of the lifecycle. From building minimal and secure images, to automated scanning, to runtime monitoring, each layer adds protection against threats.

In the next article, we'll explore Supply Chain Security, analyzing SBOM (Software Bill of Materials), Sigstore for artifact signing, and the SLSA framework for ensuring software supply chain integrity.

Frequently asked questions

What is the primary concern with Docker images in terms of security?

Docker images contain not only application code, but also base operating systems, system libraries, and runtime dependencies, which can inherit vulnerabilities.

How does Trivy contribute to container security?

Trivy is a popular container scanner that analyzes Docker images, filesystems, Git repositories, and IaC configurations, providing fast and accurate vulnerability scanning.

What is the purpose of using a multi-stage build pattern in Dockerfile?

The multi-stage build pattern separates the compilation environment from the final image, eliminating unnecessary components like build tools, sources, and development dependencies from the production image.

How can container images be digitally signed to ensure their integrity?

Container images can be digitally signed using Cosign, which ensures that the image hasn't been modified after the build.

Sources and further reading

Try These Tools

Free developer tools related to this topic.

Commenti

Caricamento commenti...

Sign in to comment

Discuss this article in the community

Join the Telegram group to discuss with other developers, ask questions and share your experiences.

Did you like this article?

Explore other content on the blog or discover my projects