Skip to main content
Technology & EngineeringContainerization201 lines

Container Security

Container image scanning, runtime hardening, and security best practices for production workloads

Quick Summary15 lines
You are an expert in container security for containerized application development and deployment.

## Key Points

- name: tmp
- name: tmp
- name: db-creds
- name: app
- Pin image digests in production (`image: myapp@sha256:abc123...`) to prevent tag mutation attacks.
- Drop all Linux capabilities and add back only the specific ones required, if any.
- Run vulnerability scans on every CI build and block deployments that introduce CRITICAL-severity CVEs.
- Scanning images only at build time but not re-scanning images already in production; new CVEs are published daily against existing packages.
- Using `latest` tags in production, which makes it impossible to audit exactly which code is running and can silently introduce vulnerable or untested changes.
skilldb get containerization-skills/Container SecurityFull skill: 201 lines
Paste into your CLAUDE.md or agent config

Container Security — Containerization

You are an expert in container security for containerized application development and deployment.

Overview

Container security spans the full lifecycle: building trusted images, scanning for vulnerabilities, enforcing least-privilege at runtime, and monitoring workloads in production. A defense-in-depth approach applies controls at the image, container, orchestrator, and host layers.

Core Concepts

Image Scanning

Scan images for known CVEs in OS packages and application dependencies before they reach production:

# Trivy — scan a local image
trivy image myapp/web:1.4.0

# Scan with severity filter and exit code for CI gating
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp/web:1.4.0

# Grype — alternative scanner
grype myapp/web:1.4.0

# Scan a Dockerfile for misconfigurations
trivy config --policy-bundle-repository ghcr.io/aquasecurity/trivy-policies ./Dockerfile

Non-Root Containers

Never run containers as root. Create a dedicated user in the Dockerfile:

FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
USER appuser
CMD ["node", "server.js"]

Read-Only Filesystems

Prevent runtime tampering by making the root filesystem read-only:

# Kubernetes SecurityContext
securityContext:
  readOnlyRootFilesystem: true
  runAsNonRoot: true
  runAsUser: 1000
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL

Provide writable volumes only where needed:

volumeMounts:
  - name: tmp
    mountPath: /tmp
volumes:
  - name: tmp
    emptyDir: {}

Secrets Management

Never embed secrets in images. Use orchestrator-native secrets or external vaults:

# Kubernetes Secret mounted as volume
volumes:
  - name: db-creds
    secret:
      secretName: db-credentials
containers:
  - name: app
    volumeMounts:
      - name: db-creds
        mountPath: /etc/secrets
        readOnly: true

Implementation Patterns

CI Pipeline Scanning

# GitHub Actions example
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

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

      - name: Run Trivy 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 results
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-results.sarif

Pod Security Standards

Enforce security baselines at the namespace level using Pod Security Admission:

apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/audit: restricted

Network Policies

Restrict pod-to-pod communication to only what is necessary:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: web
      ports:
        - port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: database
      ports:
        - port: 5432

Best Practices

  • Pin image digests in production (image: myapp@sha256:abc123...) to prevent tag mutation attacks.
  • Drop all Linux capabilities and add back only the specific ones required, if any.
  • Run vulnerability scans on every CI build and block deployments that introduce CRITICAL-severity CVEs.

Core Philosophy

Container security is a layered discipline that starts at the image and extends through the runtime, the orchestrator, and the host. No single control is sufficient; the goal is defense in depth where each layer compensates for weaknesses in the others. A vulnerability in a base image is mitigated by a non-root runtime user, which is backstopped by a read-only filesystem, which is further constrained by network policies limiting lateral movement.

The principle of least privilege applies at every layer. Containers should run as non-root users with dropped capabilities. Pods should have read-only root filesystems. Network policies should default-deny and explicitly allow only required communication paths. Secrets should be mounted read-only from external stores rather than baked into images or passed as environment variables. Each restriction narrows the blast radius of a compromise.

Security is a continuous process, not a one-time gate. Scanning an image at build time catches the vulnerabilities known today, but new CVEs are published daily against packages already in production. Continuous scanning of running images, combined with automated alerting and a clear remediation workflow, ensures that your security posture does not degrade over time. The goal is not zero vulnerabilities (which is often impossible) but a bounded, understood, and actively managed risk surface.

Anti-Patterns

  • Running containers as root by default. Many base images default to running as root. Without an explicit USER directive in the Dockerfile and runAsNonRoot: true in the pod security context, a container escape gives the attacker root access on the host. Always create and switch to a non-root user.

  • Allowing all network traffic between pods. Without network policies, every pod can communicate with every other pod in the cluster. A compromised web frontend can directly reach the database, the secret store, and every internal service. Apply default-deny network policies and explicitly allow only required paths.

  • Baking secrets into container images. Embedding API keys, database passwords, or TLS certificates in the Dockerfile or image layers makes them accessible to anyone who can pull the image. Use Kubernetes Secrets, external vault integrations, or mounted secret volumes instead.

  • Ignoring image provenance and signing. Pulling unsigned images from public registries without verifying their provenance means trusting that no one has tampered with the image between the author's build and your deployment. Use image signing (cosign, Notary) and admission controllers that enforce signature verification.

  • Using privileged: true to work around permission issues. Granting privileged mode gives the container nearly unrestricted host access and defeats every other security control. Diagnose the actual permission needed and grant only that specific capability.

Common Pitfalls

  • Scanning images only at build time but not re-scanning images already in production; new CVEs are published daily against existing packages.
  • Using latest tags in production, which makes it impossible to audit exactly which code is running and can silently introduce vulnerable or untested changes.

Install this skill directly: skilldb add containerization-skills

Get CLI access →