Docker changed how applications are deployed — but it also introduced a new attack surface that many organizations completely ignore. Containers are not VMs. They share the host kernel. A misconfigured container can lead to a complete host takeover in seconds. This guide covers container security from the ground up.
Common Container Security Mistakes
Mistake 1: Running as Root
# BAD: Default Docker behavior runs as root
docker run ubuntu id
# Output: uid=0(root) gid=0(root) groups=0(root)
# GOOD: Run as non-root user
# In Dockerfile:
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser
# Or specify at runtime:
docker run --user 1000:1000 ubuntu id
# Check which containers are running as root
docker ps -q | xargs -I{} docker inspect {} --format '{{.Id}} {{.Config.User}}' | grep -v ":"Mistake 2: Mounting Docker Socket
# EXTREMELY DANGEROUS: Mounting Docker socket gives container root on the host
# Never do this:
docker run -v /var/run/docker.sock:/var/run/docker.sock myimage
# Why it's dangerous: from inside the container with docker.sock:
docker run -v /:/host --privileged ubuntu chroot /host /bin/bash
# You now have root access to the entire HOST filesystem
# Check for exposed Docker sockets in running containers
docker ps -q | xargs -I{} docker inspect {} --format '{{.Id}} {{range .Mounts}}{{.Source}} {{end}}' | grep docker.sockMistake 3: Privileged Mode
# BAD: Privileged mode removes all security boundaries
docker run --privileged myimage
# Inside a privileged container, full host escape:
mkdir /tmp/host && mount /dev/sda1 /tmp/host
# You can now read/write the host filesystem
# Audit containers running in privileged mode:
docker ps -q | xargs -I{} docker inspect {} --format '{{.Id}} {{.HostConfig.Privileged}}' | grep "true"Scanning Container Images for Vulnerabilities
# Trivy: Fast, free container security scanner
# Install
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# Scan a container image
trivy image nginx:latest
trivy image --severity HIGH,CRITICAL python:3.9
# Scan local Docker image before pushing
docker build -t myapp:latest .
trivy image myapp:latest
# Scan a filesystem
trivy fs /path/to/project/
# Generate SBOM
trivy image --format cyclonedx --output sbom.json nginx:latest
# Scan Kubernetes cluster
trivy k8s cluster --report summary
# Grype: Another excellent free scanner
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
grype nginx:latest
grype dir:/path/to/project/Docker Bench Security
# Docker Bench: Automated CIS Docker Benchmark checker
# Checks your Docker daemon and container configurations
docker run --net host --pid host --userns host --cap-add audit_control -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST -v /etc:/etc:ro -v /var/lib:/var/lib:ro -v /var/run/docker.sock:/var/run/docker.sock:ro --label docker_bench_security docker/docker-bench-security
# Key checks it performs:
# - Docker daemon configuration
# - Running container settings
# - Docker security operations
# - Container images and build filesSecure Dockerfile Best Practices
# Secure Dockerfile example
FROM python:3.11-slim # Use specific version, never :latest
# Install security updates
RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* # Clean up to reduce attack surface
# Create non-root user
RUN groupadd -r appuser -g 1001 && useradd -r -u 1001 -g appuser appuser
WORKDIR /app
# Copy only what is needed (use .dockerignore)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=appuser:appuser . .
# Drop all capabilities
# Use only what you need in docker run --cap-add
RUN setcap -r /app/binary || true
# Switch to non-root
USER appuser
# Mark filesystem as read-only
VOLUME ["/tmp"]
# Run with: docker run --read-only --tmpfs /tmp myapp
# Health check
HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:8080/health || exit 1
EXPOSE 8080
CMD ["python", "app.py"]# .dockerignore file (prevent secrets from being included)
.git
.env
*.key
*.pem
secrets/
credentials/
.aws/
*.log
node_modules/
__pycache__/
*.pycDocker Daemon Security Configuration
# /etc/docker/daemon.json
{
"icc": false, // Disable inter-container communication by default
"no-new-privileges": true, // Prevent privilege escalation
"userns-remap": "default", // Enable user namespace remapping
"live-restore": true,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2",
"selinux-enabled": true, // Enable SELinux (RHEL/CentOS)
"seccomp-profile": "/etc/docker/seccomp-default.json"
}
# Restart Docker after config change
sudo systemctl restart docker
# Run containers with security constraints
docker run --read-only --tmpfs /tmp --cap-drop ALL --cap-add NET_BIND_SERVICE --security-opt no-new-privileges=true --security-opt seccomp=default --memory 512m --cpu-shares 512 --pids-limit 100 myapp:latestKubernetes Security Basics
# Pod Security Standards (replaces deprecated PodSecurityPolicy)
# Apply restricted policy to namespace
kubectl label namespace myapp pod-security.kubernetes.io/enforce=restricted
# Secure Pod specification
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: myapp
image: myapp:1.0.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
limits:
memory: "128Mi"
cpu: "500m"
# Scan Kubernetes for misconfigurations with kube-bench
docker run --rm -v $(which kubectl):/usr/local/mount-from-host/bin/kubectl aquasec/kube-bench:latest
# Check for exposed secrets in cluster
kubectl get secrets --all-namespaces -o json | grep -i passwordContainer security requires a shift-left mentality — scan images during CI/CD before they ever reach production. A vulnerability found during development costs minutes to fix. The same vulnerability found after a breach costs millions. Tools like Trivy integrate seamlessly into GitHub Actions, GitLab CI, and Jenkins pipelines, making automated container security scanning a zero-friction addition to any deployment workflow.