When running containers, a common security hardening practice is to execute processes as a non-root user inside the container. By default, many container images run processes as the root user. While this root inside a container isn't the root of the host system (thanks to namespaces), it still grants extensive privileges within the container's isolated environment. Should an attacker compromise a container running as root, they gain significant control, making it easier to potentially escalate privileges or further attack other services. Adopting non-root containers adheres to the principle of least privilege, drastically limiting the potential blast radius of a breach by ensuring the container process only has the permissions it absolutely needs to function.
Complementing non-root users are read-only filesystems. This security control configures the container's primary filesystem to be immutable after startup, meaning no new files can be written, existing files cannot be modified, and binaries cannot be tampered with. This capability significantly reduces an attacker's ability to persist within a compromised container, install malicious software, or modify critical application components. For applications that genuinely need to write data (e.g., logs, temporary files, uploads), this is handled by mounting separate volumes (like emptyDir or PersistentVolume) at specific paths, which are writable. This clear separation reinforces the container's immutability and makes its behavior more predictable and auditable.
Implementing non-root containers alongside read-only filesystems provides a powerful defense-in-depth strategy. Non-root users limit what a process can do, while a read-only filesystem restricts where it can write. Together, they create a robust security boundary that makes containers significantly harder to exploit and compromise. This combination reduces the attack surface, improves the overall security posture of your applications, and aligns with best practices for secure containerization, pushing towards more resilient and predictable deployments. While it requires careful planning for data persistence and application configuration, the security benefits far outweigh the setup complexity for most production workloads.
Key Takeaways
- Non-root users enforce the principle of least privilege, limiting an attacker's control within a compromised container.
- Read-only filesystems prevent unauthorized writes, modifications, or installation of malicious files inside the container.
- Combine both for a strong defense-in-depth strategy, significantly reducing the container's attack surface.
- For necessary writes (logs, temporary data), always use explicitly mounted volumes that are writable.
Code Example
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
securityContext:
runAsUser: 1000 # Run container processes as user ID 1000
runAsGroup: 1000 # Use group ID 1000
containers:
- name: my-container
image: myregistry/my-app:latest
securityContext:
readOnlyRootFilesystem: true # Make the root filesystem read-only
allowPrivilegeEscalation: false # Prevent processes gaining more privileges
volumeMounts:
- name: app-data
mountPath: /app/data # Mount a writable volume for specific data
volumes:
- name: app-data
emptyDir: {} # Example: a temporary writable volumeHow this code works
This code defines a Kubernetes Pod engineered for strong security, ensuring an application runs with minimal privileges and a largely immutable filesystem. Its primary job is to demonstrate how to restrict what an application can do and where it can write, making it harder for vulnerabilities to be exploited or for an attacker to persist changes.
The Pod achieves this by first setting a securityContext at the Pod level with runAsUser: 1000 and runAsGroup: 1000, ensuring all container processes execute as a non-root user. Inside the container's securityContext, readOnlyRootFilesystem: true locks down the main operating system files, preventing the application from modifying its own binaries or critical system components. Furthermore, allowPrivilegeEscalation: false acts as a crucial safeguard, preventing any process from gaining higher privileges within the container. A subtle but vital aspect is the inclusion of volumeMounts and volumes like emptyDir: {} for /app/data. Even with a read-only root, applications frequently need designated areas to write dynamic data or logs; this pattern provides a specific, writable location for legitimate operations without compromising the security of the core filesystem.