Docker Footguns¶
Mistakes that cause security incidents, data loss, or broken builds in production containers.
1. Running containers as root¶
Your Dockerfile has no USER directive. The container runs as root. A vulnerability in your app gives the attacker root inside the container, and with a kernel exploit or misconfigured mount, root on the host.
What happens: Container escape or unauthorized host access.
Why: Docker containers run as root by default. Root in the container maps to root on the host unless user namespaces are configured.
How to avoid: Add USER <non-root> in the Dockerfile. Enforce at runtime with --user 1000:1000. Use --security-opt=no-new-privileges.
CVE: CVE-2019-5736 (runc container escape) allowed a malicious container running as root to overwrite the host runc binary and gain host root access. This was patched, but the attack surface only existed because the container was running as root. Non-root containers were immune.
2. Using latest tag in production¶
You deploy myapp:latest on Monday. On Wednesday, someone pushes a new build to latest. A node reschedules the pod, pulls the new latest, and now you are running untested code in production.
What happens: Silent, untracked image changes. Different nodes run different versions.
Why: latest is a mutable tag. It changes every time someone pushes without a version tag.
How to avoid: Pin images by version tag or digest. Use imagePullPolicy: IfNotPresent with immutable tags.
3. No .dockerignore file¶
Your build context includes .git (500MB), node_modules (800MB), .env with secrets, and test fixtures. The build is slow and the image contains secrets.
What happens: Bloated images, slow builds, and leaked credentials baked into image layers.
Why: COPY . . sends the entire build context to the daemon. Without .dockerignore, everything goes in.
How to avoid: Create .dockerignore with .git, .env, *.pem, node_modules, __pycache__, and test data.
4. Mounting the Docker socket into a container¶
You mount /var/run/docker.sock into a CI runner or monitoring tool. That container can now create privileged containers, mount the host filesystem, and do anything root can do on the host.
What happens: Full host compromise if the container is breached.
Why: The Docker socket grants unrestricted access to the Docker daemon, which has root-equivalent privileges.
How to avoid: Never mount the Docker socket in production. For CI, use rootless Docker, Kaniko, or buildah. For monitoring, use read-only metrics endpoints.
5. Ignoring PID 1 signal handling¶
Your app does not handle SIGTERM. docker stop sends SIGTERM, waits 10 seconds, then sends SIGKILL. Every graceful shutdown takes 10 seconds minimum, and connections are dropped mid-request.
What happens: Slow shutdowns and ungraceful termination on every deploy or restart.
Why: PID 1 in Linux has special signal handling — it does not get default signal handlers. Your app must explicitly catch SIGTERM.
How to avoid: Use tini or --init to handle signals. Or handle SIGTERM in your application code. Use exec form in ENTRYPOINT: ENTRYPOINT ["python", "app.py"] not ENTRYPOINT python app.py.
Default trap: Shell form (
ENTRYPOINT python app.py) wraps your process in/bin/sh -c, makingshPID 1 and your app a child process.shdoes not forward signals to children. SIGTERM goes tosh, which ignores it by default. Your app never sees the signal. Exec form (ENTRYPOINT ["python", "app.py"]) makes your app PID 1 directly.
6. Writing important data to the container layer¶
Your app writes logs, uploads, or database files inside the container without a volume. You restart the container and everything is gone.
What happens: Data loss on every container restart or redeployment.
Why: The writable container layer is ephemeral. docker rm deletes it permanently.
How to avoid: Use named volumes for persistent data. Use bind mounts for dev configs. Never rely on the container layer for anything you need to keep.
7. Not setting memory limits¶
A container with a memory leak consumes all host memory. The kernel OOM killer fires and takes down other containers running on the same host.
What happens: One misbehaving container causes collateral damage across the host.
Why: Without --memory, a container can use unbounded memory. The OOM killer picks victims across all processes.
How to avoid: Always set --memory and --memory-swap limits. In Kubernetes, set resources.limits.memory.
8. Layer ordering that busts the cache¶
Your Dockerfile copies source code before installing dependencies. Every code change invalidates the dependency install layer, causing a full reinstall on every build.
What happens: Builds take 5 minutes instead of 30 seconds because the cache is useless.
Why: Docker invalidates all layers below a changed layer. Copying code before npm install or pip install means dependencies reinstall every time.
How to avoid: Copy dependency manifests first (package.json, requirements.txt, go.mod), install dependencies, then copy source code.
9. Using ADD when you mean COPY¶
You use ADD to copy files. Months later, someone names a file data.tar.gz and ADD silently extracts it into the image instead of copying the archive.
What happens: Unexpected file extraction, confusing build behavior, or security issues from auto-downloading URLs.
Why: ADD has implicit behaviors: it auto-extracts tar archives and can fetch URLs. COPY does exactly one thing.
How to avoid: Use COPY for everything. Only use ADD when you explicitly want tar extraction.
10. Running docker system prune -a on a production host¶
You clean up disk space with docker system prune -a. This removes all unused images. The next time a pod reschedules, it has to pull every image from the registry. If the registry is down or slow, workloads fail to start.
What happens: Mass image pulls, registry pressure, and possible outages if images are unavailable.
Why: -a removes all images not attached to a running container, not just dangling ones.
How to avoid: Use docker image prune (dangling only) on production hosts. Use docker system prune -a only in CI environments or dev machines.