Do You Think Your Containers Are Secure? No, They Are Not.

Kaan YagciKaan Yagci
4 min read

Most devs still think Docker gives them isolation. It doesn't.

Containers aren’t security boundaries. They’re process wrappers with just enough abstraction to make you feel safe. But if you're running containers with root, broad capabilities, and zero hardening, you're one kernel exploit away from turning your host into an open playground.

1. Containers Are NOT Security Boundaries

Docker leverages Linux namespaces (PID, NET, IPC, UTS, MNT, USER) and cgroups to isolate processes and control resource consumption. But this is process isolation, not security isolation.

If a container process escapes its namespace, it can interact with the host system.

Technical Detail:

  • Namespaces define what a process can see.

  • Cgroups define how much a process can use.

  • Nothing enforces "cannot affect the host" unless explicitly configured.

Example: PID Namespace Escape

If security modules aren't enforced, a container breakout exploit can escape to the host PID namespace and read or kill host processes

A visual showing how a container sees limited views via namespaces, but can still reach host kernel.

2. UID 0 in the Container == UID 0 on the Host

Containers often run as root (UID 0) inside. Unless user namespaces are enabled, this maps directly to UID 0 on the host.

Implications:

  • If a container process gains the capability to escape the namespace, you now have a root process on the host.

  • A single docker exec could be used by an attacker to pivot.

Hardening:

Use user namespace remapping:

"userns-remap": "default"

In /etc/docker/daemon.json or with --userns-remap.

This maps container UID 0 to an unprivileged UID on the host, e.g., UID 100000.

Caveats:

  • Not all images support this due to filesystem or permission assumptions.

  • Needs volume mounts to be UID-translated.

3. Containers Run with Dangerous Linux Capabilities

Docker grants a set of ~14 capabilities by default, including several that should never be present in production:

Risky Capabilities:

  • CAP_SYS_ADMIN: Mount filesystems, change network interfaces, god mode.

  • CAP_NET_RAW: Use raw sockets (sniffing, spoofing).

  • CAP_SYS_MODULE: Load/unload kernel modules.

Safer Practice:

Drop all and add only what’s needed:

--cap-drop=ALL --cap-add=NET_BIND_SERVICE

Use the principle of least privilege — containers should behave like jailed processes, not privileged guests.

Audit Tip:

Run this to list capabilities inside a container:

capsh --print

4. No Seccomp, AppArmor, SELinux? You’re Exposed

By default, Docker does not enforce MAC (Mandatory Access Control) systems like Seccomp, AppArmor, or SELinux. These systems are critical to reducing the kernel syscall and device attack surface.

Seccomp:

Filters syscalls. Prevents the use of dangerous syscalls like ptrace, keyctl, mount, etc.

Apply via:

--security-opt seccomp=/path/to/seccomp-profile.json

AppArmor (Ubuntu, Debian):

Confinement profiles per container:

--security-opt apparmor=docker-default

SELinux (RHEL, Fedora):

--security-opt label:type:container_t

Minimum Secure Profile Example:

--security-opt seccomp=default.json \
--security-opt apparmor=docker-default \
--read-only \
--no-new-privileges

This ensures containers cannot gain new privileges, even via setuid binaries or exec.

5. Still Using --privileged? You’ve Already Lost

--privileged disables all kernel isolation:

  • Grants ALL capabilities

  • Mounts all host devices

  • Disables seccomp

  • Bypasses AppArmor and SELinux

This flag is intended for debugging, not production.

If you see this in a production docker run, you're no longer containerizing. You’re just running a root process in a chroot jail.

Example: Avoid this

docker run --privileged myapp

Safer Alternative:

  • Use --device to grant specific device access

  • Use --cap-add for fine-grained capabilities

  • Always combine with --security-opt no-new-privileges

6. Bonus: Secure Container Hardening Checklist

Security FeatureStatusRecommended Action
Run as non-rootMUSTSet USER in Dockerfile or use --user
Drop Linux capabilitiesMUST--cap-drop=ALL + add only what’s needed
Enable SeccompHIGHLY RECOMMENDEDUse hardened seccomp profiles
Enable AppArmor/SELinuxHIGHLY RECOMMENDEDEnforce profiles with --security-opt
Read-only filesystemRECOMMENDED--read-only
No new privilegesRECOMMENDED--security-opt no-new-privileges
Avoid --privilegedMUSTNever use in production
Use user namespace remappingRECOMMENDEDEnable via Docker daemon config

Final thoughts

Containers are convenient, but they are not secure by default. Running containers with root access, broad capabilities, and no MAC enforcement is a recipe for lateral movement and privilege escalation.

If you’re deploying containers in production without hardening them, you’re not isolating, you’re gambling.

Take control of your container security. Audit your stack, enforce restrictions, and treat every container as if it were compromised by design.


I help engineering teams build secure, production-grade systems that can easily scale.

If your containers are running as root, missing seccomp, or leaking capabilities, you're not shipping software, you're shipping liabilities.

→ Follow me on LinkedIn for brutally practical insights
→ Subscribe to this blog for deep dives, not developer fluff
→ Reach out if your stack needs clarity, performance, or a no-bullshit architecture review

I don’t write theory. I ship battle-tested systems.

1
Subscribe to my newsletter

Read articles from Kaan Yagci directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Kaan Yagci
Kaan Yagci

Senior Platform Engineer. Infra and programming languages nerd. I write about the stuff nobody teaches: how things really work under the hood, containers, orchestration, authentication, scaling, debugging, and what actually matters when you’re building and running real systems. I share what I wish more real seniors did: the brutal, unfiltered truth about building secure and reliable systems in production.