Containers are a powerful tool, but misconfiguring them is often the cause of leaks and compromises. Below is a step-by-step checklist with examples that you can apply directly to your Docker and Kubernetes projects.
1. Build: Multi-stage and Minimal Images
Why:
The fewer packages and libraries inside a container, the fewer potential vulnerabilities.
Example:
A Go application without a multi-stage build:
FROM golang:1.22
WORKDIR /app
COPY . .
RUN go build -o app
CMD ["./app"]
Such an image will weigh ~1 GB, with lots of unnecessary packages.
With multi-stage:
# Build stage
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN go build -o app
# Runtime stage
FROM gcr.io/distroless/base
COPY --from=builder /app/app /
USER nonroot:nonroot
CMD ["/app"]
The final image will weigh <100 MB and contain only the binary.
2. Runtime: Only Non-Privileged Users
Why:
Running as root inside a container = if an attacker escapes the container, they gain root on the node.
Example in a Dockerfile:
RUN adduser -u 1001 --disabled-password appuser
USER appuser
Example in Kubernetes:
securityContext:
runAsUser: 1001
runAsNonRoot: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
3. Policies: Pod Security Admission
Why:
PSA automatically blocks the launch of insecure pods (for example, those with root privileges).
Example:
Enable the baseline policy at the namespace level:
apiVersion: v1
kind: Namespace
metadata:
name: secure-ns
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/audit: baseline
pod-security.kubernetes.io/warn: baseline
If a developer tries to run a Pod with privileged: true, Kubernetes simply won’t start it.
4. Scanning: Trivy in CI/CD
Why:
CI/CD should block the deployment of vulnerable images.
Example in GitLab CI:
stages:
- build
- security
build_image:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
scan_image:
stage: security
image: aquasec/trivy:latest
script:
- trivy image --exit-code 1 --severity HIGH,CRITICAL $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
If Trivy finds critical vulnerabilities, the pipeline will fail.
5. Network Policies: NetworkPolicy
Why:
By default, all pods in Kubernetes can talk to each other. This is a huge risk.
Example: the API can reach the DB, but not the other way around:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-allow-api
namespace: prod
spec:
podSelector:
matchLabels:
app: postgres
ingress:
- from:
- podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
Now nothing except api will be able to connect to PostgreSQL.
Conclusion
If you implement these five steps:
✅ Minimal images
✅ Running without root
✅ Pod Security Admission
✅ Automated scanning
✅ NetworkPolicy
— you’ll close off 80% of typical container security mistakes.