Locking Down the Whale: Container Security Best Practices for Production Environments
Letâs be honest for a second. Most of you are running Docker containers as root. I see it in audit logs every week. You pull an image, run it, and high-five your team because it âworks on my machine.â Meanwhile, youâve just given a potential attacker a VIP pass to your host kernel.
In the last six months, container adoption in Oslo has skyrocketed. But the security mindset hasn't caught up. We are treating containers like lightweight Virtual Machines. They aren't. They are processes with a fancy costume. If that process breaks out, and itâs running as uid 0, your entire server is compromised. With the recent invalidation of Safe Harbor and the brand new Privacy Shield agreement trying to stick the pieces back together, data sovereignty in Europe is shaky. You cannot afford a breach.
Here is how we harden containers in 2016, moving from "it works" to "itâs secure."
1. The Root of All Evil
By default, Docker containers run as root. If a vulnerability allows an attacker to break out of the container (namespace breakout), they are root on your host. This is terrifying.
The fix is simple but often ignored. Define a user in your Dockerfile. Never let the daemon default to root.
FROM debian:jessie
# Create a group and user
RUN groupadd -r app && useradd -r -g app app
# Install dependencies
RUN apt-get update && apt-get install -y --no-install-recommends ...
# Switch to user
USER app
CMD ["./my-application"]
If you are inheriting a messy image that requires root for setup, use gosu to step down from root at runtime. It handles signals better than sudo.
2. Drop Capabilities Like Theyâre Hot
The Linux kernel divides root privileges into distinct units called capabilities. Does your Nginx container really need CAP_SYS_ADMIN or CAP_NET_ADMIN? Absolutely not. It just needs to bind to a port.
Docker grants a broad set of capabilities by default. We need to follow the principle of least privilege. Whitelist only what you need. Start by dropping everything.
docker run --d --name secure-web \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
--cap-add=SETUID \
--user 1000:1000 \
nginx:latest
Pro Tip: If you are unsure which capabilities your application actually needs, you can audit it usingcapablefrom thebcc-toolscollection (if you are running a newer kernel 4.x), or simply trial and error with--cap-dropin staging. Better to break it in staging than leak data in production.
3. Kernel Isolation: Containers vs. VPS
This is where architecture decisions impact security. Containers share the host kernel. If there is a kernel panic or a zero-day exploit in the kernel syscall interface, isolation fails. This is the "noisy neighbor" problem on steroids.
For high-security environmentsâlike handling Norwegian financial data subject to Datatilsynet regulationsâyou need a harder shell. You need a hypervisor.
This is why we built CoolVDS on top of KVM (Kernel-based Virtual Machine). Unlike OpenVZ or LXC where you share a kernel with 50 other customers, KVM gives you a dedicated kernel. You can run your Docker host inside a CoolVDS NVMe instance. If your container layer is breached, the attacker is trapped inside your VPS, not roaming the bare metal infrastructure.
Comparison: Isolation Layers
| Feature | Shared Hosting / OpenVZ | Docker Container | CoolVDS (KVM) |
|---|---|---|---|
| Kernel | Shared | Shared | Dedicated |
| Attack Surface | High | Medium | Low |
| Performance | Variable | Native | Near Native (VirtIO) |
4. Network Segmentation
The --link flag is deprecated. Stop using it. Itâs a legacy feature that injects environment variables and makes your /etc/hosts messy.
Docker 1.10+ (and the current 1.12) utilizes User Defined Networks. This provides automatic DNS resolution and better isolation. Containers on separate networks cannot talk to each other, even if they are on the same host.
# Create a backend network
docker network create --driver bridge backend-net
# Run database isolated
docker run -d --net=backend-net --name db mysql:5.7
# Run app attached to both (if serving public traffic)
docker run -d --net=backend-net -p 80:80 --name app my-app
For cross-host networking, you might be looking at tools like Weave or Flannel, but keep in mind the overhead. If you are hosting in Norway to minimize latency for local users, don't kill your performance with complex overlay networks unless necessary. Sometimes, simple port binding on a private LAN (which CoolVDS offers) is faster and easier to secure with iptables.
5. Filesystem Limits and Seccomp
Prevent your containers from exhausting disk space. It sounds trivial until a runaway log file fills up /var/lib/docker and crashes the daemon. Use the --storage-opt flag if you are using the devicemapper driver, or simply mount strict volumes.
Furthermore, enable seccomp (secure computing mode). It restricts the system calls a process can make. Docker has a default profile that blocks about 44 syscalls out of 300+, but for high security, you should write your own.
Here is a snippet of what a custom profile looks like to block chmod (preventing permission changes):
{
"defaultAction": "SCMP_ACT_ALLOW",
"syscalls": [
{
"name": "chmod",
"action": "SCMP_ACT_ERRNO"
}
]
}
Run it with:
docker run --security-opt seccomp=/path/to/profile.json ...
The Infrastructure Reality
You can tweak nginx.conf and Docker flags all day, but if your underlying infrastructure is sluggish or oversold, your security hardening is wasted effort. Security scanners and encryption (SSL/TLS) eat CPU cycles. You need raw power.
At CoolVDS, we don't play games with "burstable" resources. You get dedicated cores and NVMe storage that handles high I/O workloadsâessential when you have twenty containers writing logs simultaneously. Plus, hosting here in Oslo means your data stays within Norwegian jurisdiction, simplifying your compliance with the Personal Data Act while we wait for the dust to settle on EU-US data transfers.
Next Steps:
- Audit your current running containers:
docker ps --quiet | xargs docker inspect --format '{{ .Id }}: SecurityOpt={{ .HostConfig.SecurityOpt }}' - Check your kernel version:
uname -r(Upgrade if you are below 3.10). - Move your production workload to a dedicated KVM environment.
Don't let a default config be your downfall. Secure the process, isolate the kernel, and own your infrastructure.