Containers have transformed how software is built and deployed, but ease of use has come at a cost: a typical out-of-the-box Docker or Kubernetes installation contains multiple attack surfaces that are trivially exploitable if left unaddressed. This article breaks down the most common weaknesses from both a red-team and blue-team perspective, drawing on concepts taught in practitioner-level container security courses such as the Black Hat session “From Zero to Hero: Pentesting and Securing Docker Swarm and Kubernetes Environments” led by Sheila A. Berta and Sol Ozzan.
What Are Containers and Kubernetes?
A container is a self-contained, portable unit that bundles application code together with all of its dependencies—libraries, runtimes, and configuration files. The classic developer problem containers solve is the “works on my machine” failure: because everything the application needs travels with it, a container behaves consistently regardless of where it runs.
Kubernetes is an orchestration platform that manages large numbers of containers at scale. It schedules workloads across a cluster of nodes, handles restarts, scales deployments up or down, and provides networking between containers. Managing hundreds or thousands of containers manually is impractical; Kubernetes automates that operational overhead.
Neither Docker nor Kubernetes is inherently insecure, but both ship with defaults optimised for convenience rather than security. Understanding where those defaults fall short is the first step toward hardening them.
Red Team: What Attackers Exploit
1. Containers Running as Root
The single most common misconfiguration is running container processes as the root user. When a container process runs as root and an attacker achieves code execution inside that container, they immediately have root-level privileges within the container’s filesystem and process space. This dramatically lowers the bar for subsequent exploitation, including container escape techniques.
2. The Docker Socket Bind Mount
The Docker daemon communicates through a Unix socket located at /var/run/docker.sock. If this socket is mounted into a container—a pattern sometimes used for CI/CD agents or monitoring tools—any process inside that container with root access can communicate directly with the host Docker daemon. This effectively grants the attacker control over every container in the cluster, the ability to launch new privileged containers, and the ability to mount the host filesystem. Network segmentation provides no protection once this access is achieved.
3. Secrets Stored in Environment Variables
A common shortcut is to pass sensitive values—database passwords, API tokens, TLS private keys—into containers via environment variables. Environment variables are visible to any process running inside the container and are frequently exposed through application crash dumps, debugging endpoints, or misconfigured logging. An attacker who gains even limited access to a container can often read all environment variables with a single command.
4. The Unauthenticated Docker API
The Docker Engine exposes a REST API, typically on TCP port 2375 (unencrypted) or 2376 (TLS). In a default configuration this API requires no authentication. Exposing it on a network-accessible interface allows anyone who can reach the port to enumerate all running containers, inspect their configuration, pull or push images, execute commands, and ultimately take full control of the host. Search engines such as Shodan routinely index hosts with this port open, making them trivially discoverable by attackers.
5. Post-Compromise Tool Installation
Once inside a container, attackers typically need to install reconnaissance and lateral-movement tools: network scanners, package managers, shells, or even Docker itself. A container that permits outbound network access and has a writable filesystem makes this trivial. The ability to install tools from within a compromised container is a significant force multiplier for attackers.
Blue Team: How to Harden Your Environment
1. Never Run Containers as Root
Explicitly define a non-root user in your Dockerfile using the USER directive, or override the runtime user with the --user flag when starting the container. Mapping container UIDs to unprivileged host UIDs (user namespace remapping) provides an additional layer of isolation.
# In your Dockerfile
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuserCode language: PHP (php)
2. Protect the Docker Socket
Avoid mounting /var/run/docker.sock into containers wherever possible. If your architecture genuinely requires it—for example, a container-based build agent—apply the principle of least privilege: restrict which images and operations are permitted, and run such containers in an isolated environment with no access to production workloads. The Docker documentation provides guidance on securing daemon access with TLS client certificates.
3. Use Docker Secrets Instead of Environment Variables
Docker Swarm’s built-in secrets management and Kubernetes Secrets both provide mechanisms for injecting sensitive data into containers without exposing it as a plain-text environment variable. In Docker Swarm, secrets are stored encrypted at rest and transmitted to containers over an encrypted channel, appearing as files inside the container at /run/secrets/<secret_name>. This is meaningfully better than environment variables, though Kubernetes Secrets in particular require additional hardening (encryption at rest, RBAC restrictions) to be fully secure.
4. Lock Down the Docker API
The Docker API should never be exposed on a publicly routable interface without authentication. Configure TLS mutual authentication so that only clients with a valid certificate can connect. Block TCP port 2375 at your network boundary. If the API is not needed remotely, bind it exclusively to the Unix socket and disable TCP listeners entirely.
5. Minimise the Container Attack Surface
Apply the principle of minimal images: include only the binaries, libraries, and configuration files required for the application to run. Distroless base images or Alpine-based images with a deliberate package selection make post-compromise tool installation much harder. Combine this with a read-only root filesystem where possible:
docker run --read-only --tmpfs /tmp my-image
6. Block Unused Ports and Restrict Egress
Blocking outbound connections that containers do not legitimately need limits an attacker’s ability to download tools or establish command-and-control channels after a compromise. Use Kubernetes NetworkPolicy resources or host-level firewall rules to enforce egress restrictions on a per-workload basis.
Key Takeaways
- A default Docker or Kubernetes installation prioritises convenience. Security must be deliberately configured.
- Running containers as root is the most pervasive misconfiguration and the easiest to fix.
- The Docker socket and the Docker API are high-value targets; both must be explicitly protected.
- Environment variables are an insecure secret-delivery mechanism. Use a dedicated secrets solution.
- Minimising installed packages and blocking unnecessary network access significantly reduces post-exploitation capability.
Getting Started with Hands-On Practice
The best way to internalise these concepts is to build a lab environment and attempt to exploit the misconfigurations yourself before fixing them. Docker can be installed on Linux, macOS, and Windows from the official Docker documentation. MicroK8s—a lightweight, single-node Kubernetes distribution from Canonical—is a practical option for running Kubernetes locally without the resource overhead of a full cluster. Both tools have official quick-start guides that will get you to a running environment within minutes.
Once your environment is running, consider working through the Docker security blog and the Kubernetes security concepts documentation to understand the full range of controls available to you.