Cloud Native Cybersecurity

Hardening Docker Containers at Scale: Enforcing Non-Root Users and Read-Only Filesystems

Master enterprise Docker hardening at scale by enforcing non-root users, read-only root filesystems, and tmpfs mounts to prevent container escapes.

Hardening Docker Containers at Scale: Enforcing Non-Root Users and Read-Only Filesystems - editorial cover photograph

Quick Summary / Direct Answer: Hardening Docker containers at scale requires enforcing non-root execution via the USER instruction and locking down the root filesystem with –read-only. Combined with explicit tmpfs mounts for ephemeral directories like /tmp and /var/run, this configuration stops privilege escalation attacks and container breakouts dead in their tracks, even if an application vulnerability is actively exploited.

Key Takeaways:

  • Running containers as root grants root privileges on the host kernel if a breakout vulnerability occurs.
  • Read-only filesystems block runtime payload injection, persistence, and unauthorized modifications.
  • Tmpfs mounts are mandatory to keep read-only applications running smoothly without crashing.

The Hidden Cost of Default Container Root Access

By default, Docker containers run as the root user. Most developers don’t even think about it. It just works. But when an application processes untrusted user input and falls victim to a remote code execution exploit, that default behavior turns your entire infrastructure into an open door.

If you’re running as root inside the container, you are dangerously close to running as root on the underlying host kernel. Namespace isolation helps, but kernel bugs routinely shatter those boundaries. It failed. We’ve seen it happen across production clusters worldwide. A single zero-day in a parsing library combined with a root container results in full cluster compromise.

Architectural Blueprint: Non-Root Plus Read-Only

To lock down workloads at scale, we must completely strip away write permissions from the container image’s root directory. Applications shouldn’t write to their own codebases anyway. Everything persistent belongs in external volumes, and everything temporary belongs in memory-backed tmpfs mounts.

Defining the Dockerfile User Boundary

Never rely on runtime flags alone to drop privileges. Bake the user definition directly into your Dockerfile. Here is how we configure a hardened Node.js production image:

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app

# Create a dedicated non-privileged user and group
RUN addgroup -g 1001 -S appgroup && \
    adduser -u 1001 -S appuser -G appgroup

COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules

USER appuser
EXPOSE 3000
CMD ["node", "dist/main.js"]

Notice the explicit use of --chown during the multi-stage build copy step. If you miss this, your non-root user won’t be able to read its own application files.

Enforcing Read-Only Root Filesystems at Runtime

Writing a secure Dockerfile is only half the battle. Anyone can deploy your image with privileged flags if Kubernetes Pod Security Standards or orchestrator policies aren’t strictly enforced. At runtime, we lock the filesystem using the read-only flag.

docker run -d \
  --name secured-api \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  --tmpfs /var/run:rw,noexec,nosuid,size=16m \
  --user 1001:1001 \
  secured-api:latest

If an attacker injects a web shell into your application directory, they cannot write it to disk. The filesystem rejects all write operations outside of the explicitly mounted tmpfs volumes.

Comparing Container Hardening Postures

Let’s look at how standard insecure configurations stack up against hardened enterprise deployments across critical security vectors.

Security Vector Default Container (Insecure) Hardened Enterprise Container
Runtime UID Root (UID 0) Non-Root (UID >= 1000)
Root Filesystem Read-Write (rw) Read-Only (--read-only)
Malware Persistence High (Can write backdoors to binary paths) Zero (Writes fail immediately)
Privilege Escalation Risk Critical (Direct kernel targeting path) Mitigated (Reduced syscall attack surface)

Handling Edge Cases with Tmpfs Mounts

Most legacy applications expect to write logs, cache data, or store session files inside local directories like /var/tmp or /app/storage. When you flip the read-only switch, these applications crash instantly.

Instead of rewriting legacy codebases, we map volatile memory buffers using tmpfs mounts. These mounts exist purely in RAM, providing lightning-fast I/O while ensuring that no state persists across container restarts. Furthermore, applying the noexec and nosuid flags prevents attackers from executing downloaded binaries inside your temporary directories.

Frequently Asked Questions

How do I handle applications that insist on writing to /var/log as root?

Mount a dedicated tmpfs volume to the log directory or reconfigure the application to output logs directly to stdout and stderr. Docker’s logging driver handles container stdout capture natively, making file-based logging inside production containers obsolete.

Does a read-only filesystem impact container performance?

No. In fact, using tmpfs for scratch space often improves performance because writes happen directly in system memory rather than hitting disk storage layers.

The Bottom Line: Actionable Next Steps

Audit your container fleet today. Run a quick scan across your registry to identify images executing as root. Update your base CI/CD templates to inject non-root users by default, and update your orchestration manifests to enforce readOnlyRootFilesystem: true across all Kubernetes namespaces.

Leave a Reply