Stop Trusting Default Configurations: A DevOps Guide to Container Hardening
I still remember the incident like it was yesterday. It was a cold Tuesday morning in Oslo, typical for November. A client called, frantic. Their Kubernetes cluster was spiking to 100% CPU usage on all nodes. They thought it was a DDoS attack. It wasn't. A vulnerability in an outdated node:14 base image allowed an attacker to execute a shell, escape the container namespace via a kernel exploit, and install a Monero miner on the host. This wasn't a script kiddie; it was a systematic failure of security layers.
Container security isn't just about scanning images. It's about defense in depth. If you are running mission-critical workloads in Europe, specifically here in Norway where Datatilsynet (The Norwegian Data Protection Authority) watches GDPR compliance like a hawk, you cannot afford to be lax. Default Docker settings are optimized for developer convenience, not production security.
1. The Foundation: Isolation Matters
Before we touch a single config file, we need to talk about where your containers live. Many budget hosting providers oversell resources using container-based virtualization (like OpenVZ/LXC) for their VPS products. This is a security nightmare. In those environments, you are sharing a kernel with every other tenant on that physical box. If a neighbor triggers a kernel panic, you go down. If they escape their sandbox, your data is exposed.
This is why at CoolVDS, we exclusively use KVM (Kernel-based Virtual Machine) for our NVMe instances. KVM provides hardware-level virtualization. Your kernel is your kernel. This isolation is the first line of defense against noisy neighbors and container breakouts. Combined with our local data centers in Oslo, you get the low latency required for high-frequency trading or real-time gaming without sacrificing sovereignty.
Pro Tip: Always verify your virtualization type. Run systemd-detect-virt on your server. If it says 'lxc' or 'openvz' for a production node handling sensitive PII, migrate immediately.2. Image Hygiene: Shrink the Attack Surface
The most effective way to secure a container is to remove the tools an attacker needs. If curl, wget, and bash aren't there, a reverse shell becomes significantly harder to establish. In late 2023, the standard is Distroless images or Alpine Linux (though watch out for musl libc compatibility issues).
Here is a battle-tested, multi-stage Dockerfile that compiles a Go application and runs it in a scratch container with zero external dependencies. This is how we deploy internal agents at CoolVDS:
# Build Stage
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# Build static binary with no CGO dependencies
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o secure-app .
# Final Stage
FROM scratch
# Copy CA certificates for HTTPS calls
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app/secure-app .
# Run as non-root user (ID 10001)
USER 10001
ENTRYPOINT ["./secure-app"]By using FROM scratch, there is no shell. An attacker exploiting a buffer overflow has nowhere to go.
3. Runtime Hardening: Drop Capabilities
By default, Docker containers run with a significant subset of root capabilities. They don't need most of them. Does your Nginx proxy need to change system time? No. Does your Redis cache need to load kernel modules? Absolutely not.
The golden rule is: Drop all, add back needed.
When launching a container, use the --cap-drop flag. Here is how you should run a container to ensure it cannot mess with the host network stack or filesystem ownership:
docker run --d --name web-app \n --cap-drop ALL \n --cap-add NET_BIND_SERVICE \n --read-only \n --tmpfs /tmp \n coolvds/secure-nginx:latestIn Kubernetes, this translates to the securityContext in your Pod spec. This is non-negotiable for ensuring pod-to-pod security within your cluster.
4. Immutability: The Read-Only Filesystem
If an attacker manages to compromise your application, their first move is often to download a payload or modify a configuration file. Make that impossible. Run your container's root filesystem as read-only.
However, applications often need to write temporary files (PID files, logs, caches). Map these specific paths to ephemeral storage using tmpfs. This ensures that even if malware is written to /tmp, it disappears the moment the container restarts.