Container Security in AKS: From Image to Runtime (Part 2 – Deploy & Runtime)

This article continues the overview of container security in Azure Kubernetes Service (AKS).

In the previous part, we focused on the earlier stages of the lifecycle, including image security, scanning, and registry controls.

Read part 1: Container Security in AKS: From Image to Runtime (Build & Registry)

This part focuses on deployment and runtime, where configuration and behaviour define the effective security posture.

Kubernetes Security Context

The Security Context is the Kubernetes mechanism for controlling what a container is actually allowed to do at the OS level. It is one of the most impactful and most commonly skipped hardening steps.

The key fields

  • runAsNonRoot: true – Kubernetes will refuse to start the container if the image is configured to run as root. A backstop for when Dockerfile discipline slips.
  • runAsUser – specifies the exact UID the process runs as. Pair with a consistent non-root UID in your Dockerfile.
  • readOnlyRootFilesystem: true – the container’s root filesystem is mounted read-only. This is highly effective at containing exploits: most post-exploitation techniques rely on writing to disk. A read-only filesystem removes most of that.
  • allowPrivilegeEscalation: false – prevents the process from gaining more privileges than it started with. Blocks a common privilege escalation path.
  • capabilities – Linux capabilities are fine-grained slices of what root can do. Containers get a default set that most apps don’t need. Drop all of them and add back only what’s required.

A baseline Security Context for most workloads:

securityContext:
  runAsNonRoot: true
  runAsUser: 1001
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL

Handling readOnlyRootFilesystem in practice

readOnlyRootFilesystem: true is the setting that catches teams off guard. Applications that need to write temporary files – logs, upload staging, socket files – will start failing in non-obvious ways. The fix is not to disable the setting, but to mount specific writable directories as emptyDir volumes:

volumeMounts:
  - name: tmp-dir
    mountPath: /tmp
volumes:
  - name: tmp-dir
    emptyDir:
      sizeLimit: 500Mi

emptyDir is ephemeral (wiped on pod restart) and gives the application a scoped writable area without opening up the whole filesystem. Always set a sizeLimit – an application bug that fills /tmp without a limit can affect the node.

Pod Security Standards

Kubernetes 1.25+ ships Pod Security Standards (PSS) as a built-in replacement for the deprecated PodSecurityPolicy. PSS defines three profiles – Privileged, Baseline, and Restricted – enforced at the namespace level via labels. The Restricted profile enforces most of the Security Context settings above. Enabling it on a namespace is a single label change that raises the floor for everything deployed there.

Network Policies

By default, every pod in a Kubernetes cluster can communicate with every other pod. Across namespaces. Without restriction. This is the network equivalent of putting every service on a flat LAN with no firewall rules.

Network Policies are the Kubernetes-native way to define what is and isn’t allowed. They are enforced by the CNI plugin – in AKS, Azure CNI or Calico are the common choices.

The mental model

Think of Network Policies as firewall rules for pod-to-pod traffic. They are additive – a pod with no policy applied has no restrictions. A pod with any policy can only do what the policies permit.

A practical starting point is a default-deny policy per namespace, then explicit allow rules for the traffic that should flow:

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

This blocks all ingress and egress for every pod in the namespace. You then add specific policies to open up only the paths you need – frontend to backend, backend to database, backend to external APIs.

Why this matters

The value of network policies is containment. If a service is compromised, a flat network allows the attacker to probe and pivot to every other service in the cluster. With network policies, a compromised pod can only reach what you explicitly permitted. The blast radius is bounded.

Runtime Security

Image scanning and Security Contexts are preventive controls. Runtime security is detective: it watches what’s actually happening inside running containers and alerts (or blocks) when something looks wrong.

The threat model

Even with a clean image and correct Security Contexts, an attacker can still exploit a vulnerability in the application itself. At that point, the attacker is inside a running container. Runtime security tools watch system calls, network connections, and process behaviour to detect anomalies – a web server process spawning a shell, a container making outbound connections to unexpected IPs, a process reading files it has no reason to touch.

Falco

Falco (CNCF graduated project) is the de-facto open-source runtime security tool for Kubernetes. It hooks into the Linux kernel via eBPF, reads system call events in real time, and evaluates them against a rule set. Default rules catch the obvious things: shell spawned in container, sensitive file access, privilege escalation attempts, outbound connections to non-whitelisted destinations.

Falco ships with a solid default ruleset, and rules are human-readable YAML. Alerts go to stdout by default and can be routed to SIEM, Slack, or any alerting pipeline.

Microsoft Defender for Containers

If you’re already in the Azure ecosystem, Defender for Containers is worth enabling. It covers image scanning in ACR, Kubernetes control plane audit log analysis, and runtime threat detection for AKS nodes. The signal quality is generally good for catching common attack patterns, and it integrates with Microsoft Sentinel if you’re running a centralised SIEM.

The practical recommendation: Defender for Containers as the baseline with low operational overhead, Falco for teams that need custom rules and tighter control over detection logic.

Secrets Management

This one is short because the answer is clear: do not put secrets in environment variables.

Why ENV vars are an anti-pattern

Environment variables feel convenient. They’re visible in kubectl describe pod, accessible to any process in the container, logged by many frameworks, and often appear in crash dumps and diagnostic outputs. A secret in an ENV var is a secret that has already escaped its intended boundary.

Kubernetes Secrets are only marginally better in their default form – they’re base64-encoded (not encrypted) and stored in etcd. Without etcd encryption at rest and strict RBAC on the Secrets API, they’re essentially plaintext with extra steps.

Azure Key Vault + CSI Driver

The right approach in AKS is to store secrets in Azure Key Vault and mount them into pods using the Secrets Store CSI Driver. The driver fetches the secret at pod startup, mounts it as a file or syncs it to a Kubernetes Secret, and handles rotation. The pod reads a file – it never needs to know where the secret came from. Managed identity controls access to Key Vault, so the manifest and pipeline do not require credentials.

This also means secret rotation can happen without redeployment in many cases. The CSI driver can refresh the mounted value on a schedule.

Sealed Secrets

For teams that want secrets committed to Git in encrypted form (a legitimate GitOps pattern), Sealed Secrets (by Bitnami, now part of the CNCF sandbox) encrypts a Kubernetes Secret with a cluster-specific public key. The encrypted object is safe to commit. Only the target cluster can decrypt it. It works well with ArgoCD or Flux.

Closing: Defence in Depth

No single control here makes your cluster secure. The point of this entire stack – image hygiene, scanning, Security Contexts, Network Policies, runtime detection, proper secrets management – is that each layer compensates for gaps in the others.

An attacker who slips through image scanning hits a non-root read-only container.
If an attacker exploits the application, they hit a network policy that prevents lateral movement.
If they bypass that barrier, Falco detects the activity.This is defence in depth: not one wall, but many layers where breaking through one still leaves more ahead.

The practical starting point for most teams is not to implement everything at once, but to identify which layer is currently missing entirely and close that gap first. In practice, Security Contexts and a default-deny Network Policy are the two controls that are most commonly absent and provide the highest immediate return. Start there, and build the remaining layers incrementally.

If you need additional DevOps support, our team can help with implementation, ongoing operations, and infrastructure management. Learn more on our website (Professional Services) or get in touch.

More News from Apptimized

Container Security in AKS: From Image to Runtime (Part 1 - Build & Registry)

This article focuses on container security in Azure Kubernetes Service…

Adobe Acrobat zero-day: active exploitation via PDF files

Adobe has released an emergency security update for Adobe Acrobat…

Why the Patch Management Workflow Often Becomes Reactive

Why does patch management often feel under control – until…