Container Security
Securing containerised workloads: image scanning, hardened container configurations, Kubernetes RBAC, PodSecurity admission, Falco runtime security, and network policy enforcement.
What it is
Container security addresses the unique security challenges introduced by containerised workloads: the shared kernel between containers on the same host, the container image supply chain, the Kubernetes API surface, and the dynamic, ephemeral nature of container workloads. Container security operates at four distinct layers: Image layer — scanning container images for OS package vulnerabilities, ensuring images use minimal base images (Alpine, distroless), and signing images to verify provenance; Container runtime layer — restricting container capabilities (non-root user, read-only filesystem, dropped Linux capabilities, seccomp/AppArmor profiles) to reduce the impact of a container compromise; Kubernetes configuration layer — controlling who can do what via RBAC, enforcing pod security standards via PodSecurity admission, and managing Kubernetes secrets securely; and Runtime monitoring layer — Falco, a CNCF project, monitors container syscalls at runtime and detects anomalous behaviour (shell spawned in production container, unexpected network connection, credentials file read) that indicates a compromise in progress.
Kubernetes RBAC (Role-Based Access Control) controls access to the Kubernetes API — which users and service accounts can create, read, update, or delete which Kubernetes resources. Misconfigured RBAC (overly permissive roles, cluster-admin granted broadly) is a primary vector for Kubernetes cluster compromise. PodSecurity admission (the successor to PodSecurityPolicy) enforces a set of pod-level security constraints at admission time, preventing workloads from running as root, mounting host filesystems, or using privileged mode, even if the deployment manifest requests it. The three standard profiles are privileged (no restrictions), baseline (prevents the most dangerous configurations), and restricted (most security, requires non-root, drops capabilities, read-only root filesystem).
Why it exists
Container environments introduce unique security challenges not present in traditional VM-based deployments. Containers share the host kernel — a kernel vulnerability exploitable from within a container potentially compromises the entire host and all other containers on it. A container running as root that mounts a host path has direct access to host files. The Kubernetes API is a powerful administrative interface; misconfigured RBAC or a compromised service account token can allow an attacker to deploy privileged pods, read secrets, or take over the entire cluster. The 2022 Datadog "State of Cloud Security" report found that 75% of Kubernetes containers run as root and 67% have no resource limits — both significantly increase the impact of a container compromise. Runtime security tools like Falco detect attacks that have bypassed all preventive controls by monitoring actual behaviour.
When to use
- All containerised production workloads — image scanning, non-root containers, and basic RBAC are baseline requirements.
- Kubernetes deployments — RBAC, PodSecurity admission, and NetworkPolicies are standard cluster security controls.
- Multi-tenant Kubernetes clusters — strong namespace isolation with RBAC and NetworkPolicies is mandatory.
- High-security environments — Falco runtime monitoring and OPA/Kyverno admission control policies provide defence in depth beyond the standard controls.
When not to use
- Some workloads legitimately require elevated privileges (system-level tools, eBPF programs, node-level monitoring agents like Falco itself) — these require privileged pod security profiles and should be isolated to dedicated system namespaces with restricted access.
Typical architecture
CONTAINER SECURITY LAYERS:
1. IMAGE SECURITY:
# Use minimal base image
FROM gcr.io/distroless/java17-debian12 # no shell, no package manager
# Or pinned Alpine with digest
FROM alpine:3.19@sha256:abc123...
# Trivy scan in CI:
trivy image --severity CRITICAL,HIGH --exit-code 1 myapp:latest
# Block deployment if critical/high CVEs found
# Cosign signing:
cosign sign --yes myapp@sha256:digest
2. CONTAINER RUNTIME HARDENING:
# Dockerfile: non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser # run as UID 1000, not root
# Kubernetes pod spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true # prevent writes to container fs
allowPrivilegeEscalation: false
capabilities:
drop: [ALL] # drop all Linux capabilities
add: [NET_BIND_SERVICE] # add only what's needed
seccompProfile:
type: RuntimeDefault # apply default seccomp syscall filter
3. KUBERNETES RBAC:
# Least-privilege service account
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-role
namespace: production
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config"] # specific resource, not all
verbs: ["get"] # read-only, specific resource
# Never use cluster-admin for application workloads
# ServiceAccount with no roles = safest default
4. POD SECURITY ADMISSION:
# Namespace label enforces restricted profile:
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
5. FALCO RUNTIME DETECTION:
# Example detection rules:
- rule: Shell spawned in production container
condition: spawned_process and container and
proc.name in (bash, sh, zsh) and
container.image.repo contains "myapp"
output: "Shell in container (user=%user.name cmd=%proc.cmdline)"
priority: WARNING
- rule: Unexpected outbound connection
condition: outbound and container and
not fd.sip in (known_services_ips)
priority: ERROR
6. ADMISSION CONTROL (Kyverno policy):
# Deny deployments from non-approved registries:
- match: Deployment in any namespace
deny:
conditions: image does not start with
"012345678901.dkr.ecr.us-east-1.amazonaws.com/"
Pros and cons
Pros
- Non-root containers with read-only filesystems dramatically limit the blast radius of an application-layer compromise — attackers cannot write malware to disk or escalate to root.
- PodSecurity admission prevents entire classes of misconfigurations from reaching production, even if developers accidentally commit insecure pod specs.
- Falco runtime monitoring detects in-progress attacks that have bypassed all static controls — a zero-day exploit in an application that spawns a reverse shell is detected by Falco even before a CVE is published.
- Distroless images remove the shell, package manager, and most utilities from the container — significantly reducing the attacker's toolkit after gaining container access.
Cons
- Distroless and non-root containers are harder to debug — there is no shell to exec into for troubleshooting; requires ephemeral debug containers or external tooling.
- Read-only root filesystem requires application code to explicitly write to mounted volumes or tmpfs mounts; applications that write to arbitrary filesystem paths require refactoring.
- Falco rule tuning is ongoing work; default rules generate significant noise in active environments; custom rules require deep knowledge of normal application behaviour.
- Kubernetes RBAC is complex; least-privilege RBAC for all service accounts requires significant upfront investment to identify required permissions per workload.
Implementation notes
Implement container security controls progressively using the Kubernetes PodSecurity standard levels: start by applying baseline (blocks the most dangerous configurations) to all namespaces in warn mode to identify violations without breaking workloads, fix violations, then switch to enforce mode. Then apply restricted incrementally namespace by namespace, starting with least-critical workloads and working toward production. Use the pod-security.kubernetes.io/warn and pod-security.kubernetes.io/audit labels alongside enforce to identify remaining violations.
For Kubernetes secrets management, avoid storing sensitive configuration in Kubernetes Secrets directly unless the cluster has etcd encryption at rest enabled — Kubernetes Secrets are base64 encoded, not encrypted, by default. Use an external secrets operator (ESO) to sync secrets from Vault or AWS Secrets Manager into Kubernetes Secrets, with the source of truth in a properly encrypted secrets manager rather than in etcd. Configure RBAC to restrict which service accounts can read which secrets.
Common failure modes
- Privileged containers in production:
securityContext.privileged: truegrants the container effectively root access to the host — equivalent to removing all container isolation. - hostPath volume mounts: Mounting host filesystem paths (particularly
/etc,/var/run/docker.sock) into containers allows container breakout to the host. - Default service account tokens auto-mounted: All Kubernetes pods automatically mount a service account token that can authenticate to the Kubernetes API; without RBAC restrictions, this is a lateral movement vector. Set
automountServiceAccountToken: falsefor pods that don't need API access. - Cluster-admin bindings for CI/CD: CI/CD systems that deploy to Kubernetes often use cluster-admin permissions; if the CI/CD system is compromised, the attacker has full cluster control. Use namespace-scoped roles with only the permissions required for deployment.
- Unscanned base images with critical CVEs: Using popular base images like
ubuntu:latestwithout scanning — these accumulate significant CVE counts between releases.
Decision checklist
- Are container images scanned for CVEs in CI/CD with critical findings blocking deployment?
- Do containers run as non-root users with read-only root filesystems?
- Are all Linux capabilities dropped (and only specific required ones added)?
- Is PodSecurity admission enforcing at least the baseline profile on all namespaces?
- Are Kubernetes RBAC roles following least-privilege (namespace-scoped, specific resources and verbs)?
- Is runtime security monitoring (Falco or equivalent) deployed and alerting on critical rules?
Example use cases
- Kubernetes multi-tenant platform: Platform team applies restricted PodSecurity profile to all tenant namespaces; tenant workloads must run non-root; Kyverno policies enforce image registry allowlist; NetworkPolicies prevent cross-tenant communication; Falco alerts on any shell execution in any tenant namespace.
- Financial services container hardening: All production containers use distroless images, run as UID 65534 (nobody), have read-only root filesystem with tmpfs mounts for
/tmpand log directories; seccomp RuntimeDefault profile applied; PCI DSS audit satisfied by CIS Kubernetes Benchmark compliance scan results. - Supply chain security for containers: Images built in CI, signed with Cosign using GitHub OIDC identity; Kyverno admission policy requires Cosign signature verification before any pod can start; unsigned or tampered images rejected at deployment time by the cluster.
Related patterns
- Supply Chain Security — Container image signing and scanning.
- Network Security Architecture — Kubernetes NetworkPolicies for pod-level traffic control.
- Container Orchestration — Kubernetes architecture and operational patterns.