Stop Treating Containers Like Virtual Machines
I still see it in production logs every week. A massive root process running a Node.js application inside a Docker container, exposing port 80 directly. The developer usually tells me, "But it's isolated! It's in a container!"
Here is the cold reality: Containers are process isolation, not virtualization. If a kernel exploit hits, and your container process is running as root, the attacker owns the host. In a shared hosting environment, that usually means game over. But even on dedicated instances, it means lateral movement into your database, your secrets, and your customer data.
We are late into 2020. The Schrems II ruling from July has made data sovereignty in Europe a nightmare for anyone relying strictly on US-cloud providers. If you are deploying in Norway, you need to tighten the ship. Here is how we lock down container infrastructure at the system level, and why your choice of underlying VPS architecture matters more than you think.
1. The Root Problem (Literally)
By default, Docker containers run as root. This is convenient for building, but catastrophic for running. If an attacker manages to break out of the container (via a vulnerability like CVE-2019-5736, which is still fresh in our memory), they have root on your server.
Fixing this is the single most effective step you can take. It requires changing your Dockerfile.
The Wrong Way
FROM node:14
WORKDIR /app
COPY . .
CMD ["node", "index.js"]The Production Way
Create a specific user. Don't use the default.
FROM node:14-alpine
# Create a group and user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY . .
# Chown files so the user can access them
RUN chown -R appuser:appgroup /app
# Switch to the user
USER appuser
CMD ["node", "index.js"]Pro Tip: In Kubernetes, enforce this at the cluster level. Use a PodSecurityPolicy (PSP) to reject any pod that attempts to run as root. If you aren't using PSPs in 2020, you are operating without a safety net.2. Minimize the Attack Surface: Distroless Images
Why does your production web server need curl, wget, or bash installed? It doesn't. These tools are gifts to attackers. If they gain RCE (Remote Code Execution), they use these tools to download crypto-miners or exfiltrate data.
We are seeing a massive shift this year towards Google's Distroless images. They contain only your application and its runtime dependencies. No shell. No package manager.
# Build stage
FROM golang:1.14 as build
WORKDIR /go/src/app
COPY . .
RUN go build -o main .
# Production stage
FROM gcr.io/distroless/static
COPY --from=build /go/src/app/main /
CMD ["/main"]If an attacker gets in, they have no shell to execute commands. It turns a breach into a dead end.
3. Read-Only Filesystems
Most applications don't need to write to the root filesystem. They should be writing logs to `stdout`/`stderr` (which the logging driver handles) and data to mounted volumes.
Make the container immutable. This prevents an attacker from downloading a malicious binary and making it executable.
Docker CLI
docker run --read-only -v /my/data:/data my-appKubernetes Context
In your deployment YAML, set the securityContext:
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
containers:
- name: my-secure-container
image: my-app:1.0
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- mountPath: /tmp
name: tmp-volume
volumes:
- name: tmp-volume
emptyDir: {}Notice I mounted /tmp? Many apps crash if they can't write to temporary directories. Give them a scratchpad, but keep the OS locked.