The Container Security Paradox
Containers feel secure because they are isolated. You pull an image, run it, and it operates in its own namespace with its own filesystem. But this perception of security is misleading. By default, Docker containers run as root, share the host kernel, and can potentially escape their sandbox if misconfigured. A compromised container running as root with the --privileged flag is effectively a root shell on your host machine.
Docker security is not a feature you enable — it is a discipline you practice. This guide walks through every major security concern, from the images you build to the containers you run, and provides concrete steps to harden your Docker deployment.
Never Run Containers as Root
This is the single most important Docker security practice. By default, the process inside a container runs as root (UID 0). While container namespaces provide some isolation, a kernel vulnerability or container escape exploit gives an attacker root access to the host.
Method 1: USER Directive in Dockerfile
Method 2: Runtime User Override
docker exec mycontainer whoami on every running container. If the answer is "root," you have work to do.
Rootless Docker: Defense in Depth
Even better than running containers as non-root is running the Docker daemon itself as a non-root user. This is called Rootless Docker, and it means that even if an attacker escapes the container, they only have the privileges of a regular user — not root.
Rootless Docker Limitations
| Feature | Regular Docker | Rootless Docker |
|---|---|---|
| Port binding < 1024 | Yes | No (use port > 1024) |
| Host networking | Yes | No |
| Overlay filesystems | Yes | Limited (fuse-overlayfs) |
| Cgroup management | Full | Cgroups v2 only |
| Container escape impact | Root access | User access only |
Read-Only Containers
If your application does not need to write to the filesystem, make the entire container read-only. This prevents attackers from dropping malware, modifying binaries, or creating backdoors inside a compromised container.
The tmpfs mounts provide writable temporary storage in RAM for directories that the application needs to write to (like /tmp for session files or /var/run for PID files). These are ephemeral — data is lost when the container stops.
Image Scanning: Find Vulnerabilities Before Production
Every Docker image is built on layers of software — base OS packages, runtime libraries, application dependencies. Any of these can contain known vulnerabilities. Image scanning tools check every package against vulnerability databases (CVE lists) and alert you to issues.
Popular Scanning Tools
| Tool | Type | Cost | Integration |
|---|---|---|---|
| Docker Scout | Built into Docker CLI | Free tier | Docker Desktop, CLI, CI/CD |
| Trivy | Open source (Aqua) | Free | CLI, CI/CD, Kubernetes |
| Grype | Open source (Anchore) | Free | CLI, CI/CD |
| Snyk Container | SaaS + CLI | Freemium | IDE, CI/CD, registries |
| Clair | Open source (Quay) | Free | Registry integration |
Scanning in Practice
Minimal Base Images: Smaller Is Safer
Every package in your image is a potential attack surface. The fewer packages you include, the fewer vulnerabilities you expose. Choose base images deliberately.
| Base Image | Size | Packages | Shell? | Best For |
|---|---|---|---|---|
ubuntu:24.04 | ~78 MB | ~100+ | Yes | Development, debugging |
alpine:3.19 | ~7 MB | ~15 | Yes (ash) | General production use |
distroless | ~2-20 MB | ~0 | No | Maximum security |
scratch | 0 MB | 0 | No | Static binaries (Go, Rust) |
Using Distroless Images
Google's distroless images contain only the application runtime and its dependencies — no shell, no package manager, no utilities. An attacker who gains code execution cannot run bash, curl, wget, or any other tool.
Never Use --privileged
The --privileged flag is the nuclear option. It gives the container full access to all host devices, disables all security restrictions (AppArmor, seccomp, capabilities), and allows the container to modify the host kernel. A privileged container is effectively identical to running as root on the host.
Linux Capabilities: Fine-Grained Privileges
Instead of --privileged, Docker supports adding and dropping individual Linux capabilities:
| Capability | Purpose | Keep? |
|---|---|---|
NET_BIND_SERVICE | Bind ports below 1024 | If needed |
CHOWN | Change file ownership | Rarely |
SYS_ADMIN | Mount filesystems, admin operations | Almost never |
SYS_PTRACE | Debug other processes | Development only |
NET_RAW | Raw sockets (ping) | Usually not |
SYS_MODULE | Load kernel modules | Never |
--cap-drop ALL and add back only what your application actually needs. Most applications need zero additional capabilities beyond Docker's already-reduced default set.
Seccomp Profiles: System Call Filtering
Seccomp (Secure Computing Mode) filters which system calls a container can make. Docker includes a default seccomp profile that blocks approximately 44 of the 300+ Linux system calls. You can create custom profiles for even tighter restrictions.
AppArmor and SELinux
Docker integrates with Linux Security Modules (LSM) to provide mandatory access control. On Ubuntu/Debian systems, this is AppArmor; on RHEL/CentOS/Fedora, it is SELinux.
AppArmor (Ubuntu/Debian)
Docker applies a default AppArmor profile (docker-default) to every container. This profile restricts file access, mount operations, and network capabilities. Custom profiles can further restrict container behavior.
SELinux (RHEL/Fedora)
With SELinux in enforcing mode, Docker applies the container_t type to containers. This prevents containers from accessing host files, even if mounted improperly.
Secrets Management
Credentials, API keys, and certificates must never be baked into Docker images or passed as plain environment variables (visible in docker inspect).
Anti-Patterns vs Best Practices
Bad Hardcoded in Dockerfile
Visible in image layers, Docker history, and any registry. Anyone who pulls the image gets the credentials.
Good Runtime Secrets
Application reads from file. Secret is not in image, not in environment, and the mount is read-only.
Resource Limits: Preventing Denial of Service
Without resource limits, a single misbehaving container can consume all host CPU, memory, or disk I/O, effectively taking down every other container on the same host.
| Flag | Purpose | Recommended For |
|---|---|---|
--memory | Hard memory limit | Always set |
--memory-swap | Memory + swap limit (set equal to --memory to disable swap) | Always set |
--cpus | CPU core limit (e.g., 1.5 = 1.5 cores) | Always set |
--pids-limit | Maximum process count (prevents fork bombs) | Always set |
--ulimit nofile=1024 | File descriptor limit | As needed |
--storage-opt size=10G | Container writable layer size limit | If available |
Docker Content Trust: Image Signing
Docker Content Trust (DCT) ensures that the images you pull are exactly what the publisher intended — not tampered with in transit or at rest in the registry.
Docker Bench Security: Automated Auditing
Docker Bench for Security is an official script that checks dozens of common security best practices against your Docker host and containers. It is based on the CIS Docker Benchmark.
Supply Chain Security Checklist
- Pin image versions with digests (
nginx:alpine@sha256:abc...), never use:latestin production - Use official images or verified publishers from Docker Hub
- Scan images in CI/CD pipeline before deployment
- Enable Docker Content Trust for image signing verification
- Keep base images updated (automate with Dependabot or Renovate)
- Review Dockerfiles for secrets, unnecessary packages, and root usage
- Use multi-stage builds to exclude build tools from production images
- Store images in a private registry with access controls
- Implement image admission policies in your orchestrator
- Audit and rotate container credentials regularly
Docker Security with Panelica
Panelica enforces container isolation with per-container Cgroups v2 resource limits and integrates Docker containers within the panel's 5-layer security isolation architecture. Every Docker container deployed through Panelica is automatically placed within the user's cgroup slice, inheriting CPU, memory, I/O, and process count limits. This means even if a container attempts to consume unlimited resources, the cgroup enforcement stops it before it affects other users or the host system.
The panel's Docker module also enforces RBAC: each user can only see and manage containers labeled with their user ID. Root and admin users have broader visibility based on their role hierarchy, but no user can accidentally — or deliberately — interact with another user's containers.
Security Hardening Checklist Summary
| Category | Action | Priority |
|---|---|---|
| User | Run as non-root (USER in Dockerfile) | Critical |
| Capabilities | Drop all, add only needed | Critical |
| Filesystem | Use --read-only with tmpfs | High |
| Images | Scan for CVEs in CI/CD | Critical |
| Base Images | Use minimal (Alpine, distroless) | High |
| Resources | Set memory, CPU, PID limits | Critical |
| Secrets | Never hardcode, use file mounts | Critical |
| Network | Bind to 127.0.0.1, use internal networks | High |
| Privileged | Never use --privileged | Critical |
| Signing | Enable Docker Content Trust | High |
Conclusion
Docker security is not about a single configuration — it is a layered defense strategy. Start with the highest-impact changes: run containers as non-root, scan images for vulnerabilities, set resource limits, and never use --privileged. Then layer on read-only filesystems, minimal base images, seccomp profiles, and image signing for comprehensive protection.
The principle of least privilege applies to every aspect of container security. Drop all capabilities and add back only what you need. Use internal networks and bind ports to localhost. Mount filesystems as read-only. Choose distroless over Ubuntu. Every unnecessary permission you remove is one less attack vector an adversary can exploit. Security is not a destination — it is a continuous practice of reducing your attack surface while monitoring for new threats.