Skip to main content
โšก Calmops

Container Security Fundamentals: Images, Registry, and Runtime

Containers have transformed how we deploy applications, but they also introduce new security challenges. From vulnerable base images to privileged containers, understanding container security is critical.

In this guide, we’ll explore container security at every layer: images, registries, and runtime.

The Container Attack Surface

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              Container Security Layers                        โ”‚
โ”‚                                                             โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚   โ”‚              Runtime Security                         โ”‚  โ”‚
โ”‚   โ”‚   - Container isolation                             โ”‚  โ”‚
โ”‚   โ”‚   - Resource limits                                 โ”‚  โ”‚
โ”‚   โ”‚   - Runtime protection                              โ”‚  โ”‚
โ”‚   โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”‚
โ”‚   โ”‚              Registry Security                       โ”‚  โ”‚
โ”‚   โ”‚   - Access control                                  โ”‚  โ”‚
โ”‚   โ”‚   - Image signing                                   โ”‚  โ”‚
โ”‚   โ”‚   - Vulnerability scanning                          โ”‚  โ”‚
โ”‚   โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”‚
โ”‚   โ”‚              Image Security                          โ”‚  โ”‚
โ”‚   โ”‚   - Minimal base images                             โ”‚  โ”‚
โ”‚   โ”‚   - No secrets in images                           โ”‚  โ”‚
โ”‚   โ”‚   - Regular updates                                 โ”‚  โ”‚
โ”‚   โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”‚
โ”‚   โ”‚              Build Security                          โ”‚  โ”‚
โ”‚   โ”‚   - SBOM generation                                 โ”‚  โ”‚
โ”‚   โ”‚   - Supply chain security                           โ”‚  โ”‚
โ”‚   โ”‚   - CI/CD pipeline security                        โ”‚  โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Image Security

Secure Image Building

# BAD: Large base image with secrets
FROM ubuntu:latest
RUN apt-get update && apt-get install -y python
COPY . /app
ENV API_KEY=secret123  # NEVER DO THIS!

# GOOD: Minimal image, no secrets
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
USER nonroot:nonroot

Image Best Practices

# Multi-stage build for smaller images

# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# Production stage  
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
USER node

# Never run as root!
USER nonroot:nonroot

# Use specific versions, not :latest
FROM python:3.12.2-slim

# Scan for vulnerabilities in build
# RUN /usr/local/bin/trivy image myapp:latest

Secret Management

# BAD: Secrets in image
# docker-compose.yml
services:
  app:
    image: myapp
    environment:
      - DATABASE_PASSWORD=secret  # NEVER!

# GOOD: Use secrets management
services:
  app:
    image: myapp
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt  # Or external secrets manager
# Kubernetes secrets
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
stringData:
  username: admin
  password: ${DB_PASSWORD}  # Injected from vault

Registry Security

Private Registry Security

# Registry access control

authentication:
  # Use strong authentication
  - Type: "OIDC or LDAP"
    Provider: "Keycloak or Okta"
  
  # Enable anonymous access only for public images
  anonymous_access: false

scanning:
  # Scan on push
  on_push: true
  
  # Block vulnerable images
  fail_on_severity: "HIGH"
  
  # Scan schedule for existing images
  periodic_scan: "daily"

Image Signing

# Sign images with Cosign (Sigstore)

# Install Cosign
brew install cosign

# Generate key pair
cosign generate-key-pair

# Sign image
cosign sign myregistry.io/myapp:latest

# Verify signature
cosign verify myregistry.io/myapp:latest

# Store in registry transparency log
cosign store myregistry.io/myapp:latest

Vulnerability Scanning

# Using Trivy

# Scan image
trivy image myapp:latest

# Scan with severity filter
trivy image --severity HIGH,CRITICAL myapp:latest

# Scan in CI/CD
trivy image \
  --exit-code 1 \
  --severity CRITICAL \
  --ignore-unfixed \
  myapp:latest

# Scan Dockerfile for best practices
trivy config my-dockerfile/

# Generate SBOM
trivy sbom myapp:latest --format spdx

Runtime Security

Container Runtime Protection

# Docker security options

# Run as non-root
securityContext:
  runAsNonRoot: true
  runAsUser: 10000
  runAsGroup: 10000
  fsGroup: 10000

# Drop all capabilities, add only what's needed
securityContext:
  capabilities:
    drop:
      - ALL
    add:
      - NET_BIND_SERVICE

# Read-only root filesystem
securityContext:
  readOnlyRootFilesystem: true

# Prevent privilege escalation
securityContext:
  allowPrivilegeEscalation: false

Kubernetes Security Context

apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 10000
    fsGroup: 10000
    seccompProfile:
      type: RuntimeDefault
  
  containers:
  - name: app
    image: myapp:latest
    
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop: ["ALL"]
    
    resources:
      limits:
        memory: "128Mi"
        cpu: "500m"
      requests:
        memory: "64Mi"
        cpu: "250m"
    
    volumeMounts:
    - name: tmp
      mountPath: /tmp

  volumes:
  - name: tmp
    emptyDir: {}

Network Policies

# Kubernetes Network Policies

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-app-to-db
spec:
  podSelector:
    matchLabels:
      tier: database
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          tier: app
    ports:
    - protocol: TCP
      port: 5432

Resource Limits

# Resource quotas and limits

apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
spec:
  hard:
    requests.cpu: "4"
    limits.cpu: "8"
    requests.memory: 8Gi
    limits.memory: 16Gi
    pods: "20"

---
apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
spec:
  limits:
  - default:
      memory: "256Mi"
      cpu: "250m"
    defaultRequest:
      memory: "128Mi"
      cpu: "100m"
    type: Container

Supply Chain Security

SBOM (Software Bill of Materials)

# Generate SBOM

# Using Syft
syft myapp:latest -o json > sbom.json
syft myapp:latest -o spdx > sbom.spdx

# Using Trivy
trivy sbom myapp:latest --format spdx-json > sbom.json

# Verify against known vulnerabilities
syft myapp:latest | grype

Supply Chain Security Tools

# SLSA (Supply-chain Levels for Software Artifacts)

slsa_levels = {
    "level_1": {
        "description": "Documentation",
        "requirements": "Build process is documented"
    },
    
    "level_2": {
        "description": "Hosted build",
        "requirements": "Build runs on hosted service"
    },
    
    "level_3": {
        "description": "Generated",
        "requirements": "Build system generates provenance"
    },
    
    "level_4": {
        "description": "Hardened",
        "requirements": "Build is reproducible, auditable"
    }
}

# Tools to achieve SLSA:
# - Cosign: Image signing
# - Tekton: Secure build pipelines  
# - Sigstore: Open source signing
# - SBOM generators: Syft, Trivy

Security Scanning in CI/CD

# GitHub Actions - Container security

name: Security Scan
on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Build Docker image
      run: docker build -t myapp:${{ github.sha }} .
    
    - name: Run Trivy scanner
      uses: aquasecurity/trivy-action@master
      with:
        image-ref: 'myapp:${{ github.sha }}'
        format: 'sarif'
        severity: 'CRITICAL,HIGH'
        exit-code: '1'
    
    - name: Run Cosign sign
      uses: sigstore/cosign-installer@main
      with:
        cosign-version: 'v2.0.0'
      run: |
        cosign sign --yes myregistry.io/myapp:${{ github.sha }}

Runtime Protection

Falco - Runtime Security

# Falco rules

rules:
  - rule: "Detect shell in container"
    desc: "A shell was spawned in a container"
    condition: >
      evt.type = execve and 
      evt.arg[0] = /bin/sh
    output: "Shell spawned in container (user=%user.name command=%proc.cmdline container_id=%container.id)"
    priority: WARNING

  - rule: "Detect privileged container"
    desc: "Privileged container detected"
    condition: >
      container.privileged = true
    output: "Privileged container (command=%proc.cmdline image=%container.image.repository)"
    priority: CRITICAL

  - rule: "Sensitive file access"
    desc: "Access to sensitive files"
    condition: >
      fd.name in (/etc/shadow, /etc/passwd) 
      and container.id != host
    output: "Sensitive file access (file=%fd.name user=%user.name)"
    priority: WARNING

Seccomp and AppArmor

# Seccomp profile (Kubernetes)

apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/myprofile.json

# AppArmor profile
/etc/apparmor.d/usr.sbin.myapp

Conclusion

Container security requires defense in depth:

  • Images: Minimal base, no secrets, frequent updates
  • Registry: Access control, signing, scanning
  • Runtime: Security contexts, network policies, limits
  • Supply chain: SBOM, SLSA, signing

Automate security scanning in CI/CD and enforce policies in production.


Comments