Hello Security
Run nginx with secure container defaults: non-root user, read-only filesystem, no capabilities.
Time: ~5 minutes Difficulty: Beginner
What You Will Learn
Section titled “What You Will Learn”- Setting
runAsNonRoot: trueto block root execution - Using
readOnlyRootFilesystem: trueto prevent filesystem writes - Dropping all Linux capabilities with
capabilities.drop: [ALL] - Setting
allowPrivilegeEscalation: false - Using non-root base images like
nginxinc/nginx-unprivileged - Mounting writable volumes for cache and runtime directories
Deploy
Section titled “Deploy”Navigate to the demo directory:
cd demos/hello-securityDeploy the resources:
kubectl apply -f manifests/namespace.yamlkubectl apply -f manifests/Verify
Section titled “Verify”# Check pods are runningkubectl get pods -n hello-security
# Check the servicekubectl get svc -n hello-security
# Access the appkubectl port-forward svc/secure-nginx-service 8080:80 -n hello-securityOpen http://localhost:8080 in your browser. You should see the default nginx welcome page.
Verify the security settings:
# Confirm the pod is running as non-rootkubectl exec deployment/secure-nginx -n hello-security -- id# uid=101 gid=101
# Try to write to the filesystem (should fail)kubectl exec deployment/secure-nginx -n hello-security -- touch /test# touch: /test: Read-only file system
# Writable volumes still workkubectl exec deployment/secure-nginx -n hello-security -- touch /var/cache/nginx/test# WorksWhat is Happening
Section titled “What is Happening”manifests/ namespace.yaml # hello-security namespace deployment.yaml # nginx with full security hardening service.yaml # ClusterIP service on port 80The Deployment runs nginx with all security best practices enabled:
| Setting | What It Does |
|---|---|
runAsNonRoot: true | Prevents running as UID 0 (root) |
runAsUser: 101 | Sets the user ID to 101 (nginx user in unprivileged image) |
readOnlyRootFilesystem: true | Makes the container filesystem read-only |
allowPrivilegeEscalation: false | Blocks privilege escalation via setuid/setgid binaries |
capabilities.drop: [ALL] | Removes all Linux capabilities |
seccompProfile: RuntimeDefault | Restricts syscalls to a safe set |
Why use nginxinc/nginx-unprivileged?
The official nginx image runs on port 80, which requires root privileges. The unprivileged variant listens on port 8080 and is designed to run as a non-root user. This is the recommended approach for production workloads.
Why mount emptyDir volumes?
With readOnlyRootFilesystem: true, nginx cannot write to /var/cache/nginx or /var/run. We mount writable emptyDir volumes at these paths so nginx can store temporary files and its PID file.
Experiment
Section titled “Experiment”-
Compare with an insecure nginx pod:
Terminal window kubectl run insecure-nginx --image=nginx:1.25-alpine -n hello-securitykubectl exec insecure-nginx -n hello-security -- id# uid=0(root)kubectl delete pod insecure-nginx -n hello-security -
Try to escalate privileges (will fail):
Terminal window kubectl exec deployment/secure-nginx -n hello-security -- cat /proc/1/status | grep Cap# All capabilities are 0 (none) -
View the security context in the running pod:
Terminal window kubectl get pod -n hello-security -l app=secure-nginx -o jsonpath='{.items[0].spec.securityContext}' | jq
Cleanup
Section titled “Cleanup”kubectl delete namespace hello-securityFurther Reading
Section titled “Further Reading”See docs/deep-dive.md for a detailed explanation of Linux capabilities, seccomp profiles, why read-only filesystems matter, and how these settings protect against container escapes and privilege escalation attacks.
Next Step
Section titled “Next Step”This is the final demo in the series. To continue learning, explore:
- Pod Security for Pod Security Standards and admission control
- Kyverno for policy-based enforcement of these settings
- Falco for runtime threat detection