By FixTheVuln Team
Peer-reviewed security content
Sources: CISA, NVD, OWASP
Key Takeaways
- Scan container images for vulnerabilities before deployment using Trivy or Snyk
- Run containers as non-root users — never use the root user inside containers
- Use minimal base images (Alpine, distroless) to reduce attack surface
- Never store secrets in container images or environment variables in Dockerfiles
- Implement network policies in Kubernetes to restrict pod-to-pod communication
Test Your Knowledge
Latest from the Blog
Container Security Essentials
Quick reference for securing Docker containers, Kubernetes clusters, and container registries. Covers image hardening, runtime security, and orchestration best practices.
Docker Security Hardening
1. Image Security
# Use minimal base images
FROM alpine:3.18
# or
FROM gcr.io/distroless/static-debian11
# Don't run as root
RUN addgroup -g 1000 appgroup && \
adduser -u 1000 -G appgroup -D appuser
USER appuser
# Use specific versions, not 'latest'
FROM node:20.10.0-alpine3.18
# Multi-stage builds to reduce attack surface
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o myapp
FROM scratch
COPY --from=builder /app/myapp /myapp
ENTRYPOINT ["/myapp"]
2. Dockerfile Best Practices
# Bad - running as root
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y nginx
CMD ["nginx", "-g", "daemon off;"]
# Good - non-root user, minimal image
FROM nginx:1.25-alpine
RUN addgroup -g 101 -S nginx && \
adduser -S -D -H -u 101 -h /var/cache/nginx -s /sbin/nologin -G nginx nginx
USER nginx
COPY --chown=nginx:nginx nginx.conf /etc/nginx/nginx.conf
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]
3. Docker Daemon Security
# /etc/docker/daemon.json
{
"icc": false,
"userns-remap": "default",
"no-new-privileges": true,
"live-restore": true,
"userland-proxy": false,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2"
}
4. Runtime Security Flags
# Run container with security options
docker run -d \
--name secure-app \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=100m \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges:true \
--security-opt seccomp=/path/to/seccomp-profile.json \
--pids-limit 100 \
--memory 512m \
--cpus 0.5 \
--user 1000:1000 \
--network custom-network \
myapp:latest
Kubernetes Security
1. Pod Security Standards
# Restricted Pod Security Standard
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:v1.0.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
limits:
memory: "128Mi"
cpu: "500m"
requests:
memory: "64Mi"
cpu: "250m"
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
2. Network Policies
# Default deny all ingress traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
---
# Allow specific ingress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
3. RBAC Configuration
# Least privilege Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
# RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: production
subjects:
- kind: ServiceAccount
name: monitoring-sa
namespace: production
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
4. Secrets Management
# Use external secrets operator (not plain K8s secrets)
# Example with External Secrets Operator
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: secret/data/production/db
property: username
- secretKey: password
remoteRef:
key: secret/data/production/db
property: password
Image Scanning & Registry Security
1. Trivy Scanner
# Scan image for vulnerabilities
trivy image myapp:latest
# Scan with severity filter
trivy image --severity HIGH,CRITICAL myapp:latest
# Scan and fail on vulnerabilities (for CI/CD)
trivy image --exit-code 1 --severity CRITICAL myapp:latest
# Scan Dockerfile
trivy config Dockerfile
# Scan Kubernetes manifests
trivy config --severity HIGH,CRITICAL ./k8s/
# Generate SBOM
trivy image --format spdx-json -o sbom.json myapp:latest
2. Grype Scanner
# Scan image
grype myapp:latest
# Scan with output format
grype myapp:latest -o json > vulnerabilities.json
# Fail on severity
grype myapp:latest --fail-on critical
# Scan SBOM
grype sbom:./sbom.json
3. CI/CD Integration (GitHub Actions)
name: Container Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
4. Registry Security
# Sign images with cosign
cosign generate-key-pair
cosign sign --key cosign.key myregistry.com/myapp:v1.0.0
# Verify signed image
cosign verify --key cosign.pub myregistry.com/myapp:v1.0.0
# Use admission controller to enforce signed images
# Kyverno policy example
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
background: false
rules:
- name: verify-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "myregistry.com/*"
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
Runtime Security
1. Falco Rules
# /etc/falco/falco_rules.local.yaml
- rule: Detect Shell in Container
desc: Detect shell spawned in container
condition: >
spawned_process and
container and
shell_procs
output: >
Shell spawned in container
(user=%user.name container=%container.name
shell=%proc.name parent=%proc.pname)
priority: WARNING
tags: [container, shell]
- rule: Detect Crypto Mining
desc: Detect crypto mining processes
condition: >
spawned_process and
container and
(proc.name in (cryptominer_binaries) or
proc.cmdline contains "stratum+tcp")
output: >
Crypto mining detected
(container=%container.name command=%proc.cmdline)
priority: CRITICAL
tags: [container, cryptomining]
2. AppArmor Profile
# /etc/apparmor.d/docker-custom
#include
profile docker-custom flags=(attach_disconnected,mediate_deleted) {
#include
network,
capability,
deny @{PROC}/* w,
deny @{PROC}/sys/kernel/** w,
deny /sys/** w,
deny /bin/su x,
deny /usr/bin/sudo x,
deny /bin/bash x,
deny /bin/sh x,
/app/** r,
/app/bin/* ix,
/tmp/** rw,
}
# Load profile
sudo apparmor_parser -r /etc/apparmor.d/docker-custom
# Use in Docker
docker run --security-opt apparmor=docker-custom myapp:latest
3. Seccomp Profile
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": [
"accept", "access", "arch_prctl", "bind", "brk",
"clone", "close", "connect", "dup", "dup2",
"execve", "exit", "exit_group", "fcntl", "fstat",
"futex", "getdents64", "getpid", "getsockname",
"getsockopt", "listen", "lseek", "mmap", "mprotect",
"munmap", "nanosleep", "open", "openat", "pipe",
"poll", "read", "recvfrom", "rt_sigaction",
"rt_sigprocmask", "sendto", "set_tid_address",
"setsockopt", "socket", "stat", "write"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
Container Security Checklist
| Category | Check | Priority |
|---|---|---|
| Images | Use minimal base images (Alpine, Distroless) | High |
| Images | Pin image versions, avoid 'latest' tag | High |
| Images | Scan images for vulnerabilities in CI/CD | Critical |
| Images | Sign and verify images | High |
| Runtime | Run containers as non-root user | Critical |
| Runtime | Use read-only root filesystem | High |
| Runtime | Drop all capabilities, add only needed | High |
| Runtime | Set resource limits (CPU, memory, PIDs) | Medium |
| Kubernetes | Implement Pod Security Standards | Critical |
| Kubernetes | Use Network Policies (default deny) | High |
| Kubernetes | Configure RBAC with least privilege | Critical |
| Kubernetes | Use external secrets management | High |
| Registry | Use private registry with authentication | High |
| Registry | Enable vulnerability scanning on push | High |
| Monitoring | Deploy runtime security (Falco, Sysdig) | Medium |
| Monitoring | Enable audit logging | High |
Related Resources
Need Detailed Container Security Guides?
For comprehensive tutorials and step-by-step implementation guides:
Visit FixTheVuln.com →FixTheVuln Store
Get the Kubernetes CKS Study Planner
Fillable PDF study planners with domain trackers, weekly schedules, and progress tracking. Available in Standard, ADHD-Friendly, Dark Mode, and 4-Format Bundle.
Kubernetes CKS Planner60+ certifications available — from $5.99
CyberFolio
Choosing your next cert? Track them all in one place.
Build a shareable cybersecurity portfolio that highlights your certifications, projects, and skills — free.
Build Your Portfolio →