Docker deployment security checklist
Every item in this checklist maps to an OWASP rule, a CIS Docker Benchmark control, or a finding Keelix catches automatically. Work through it once before you ship; automate it in CI so it runs every time.
Image hardening
Run as a non-root user
Add a USER instruction in your Dockerfile. The container default is root (uid 0). If an attacker escapes the container, they have host root privileges unless you map to an unprivileged uid. OWASP Rule #2.
Use a minimal base image
Prefer distroless, alpine, or slim variants. Fewer packages means fewer CVEs in the base layer and a smaller attack surface. Verify with `docker scan` or `keelix scan`.
No secrets baked into the image
Never COPY a .env file or hard-code credentials in a Dockerfile ENV instruction. Anything in a layer is readable with `docker history`. Use build secrets (BuildKit) or inject at runtime via Docker secrets.
Pin image tags to a digest
FROM node:20-alpine is mutable — the tag can be silently updated with a different image. FROM node:20-alpine@sha256:<digest> is immutable. Pin digests for production images.
Scan for CVEs in the image
Run a vulnerability scanner (Trivy, Grype, or keelix scan) against the image before deployment. Block on critical findings. OWASP Rule #9.
Runtime configuration
Drop all capabilities; re-add only what's required
--cap-drop=ALL removes every Linux capability. Then --cap-add=NET_BIND_SERVICE re-adds only what that container legitimately needs. Most application containers need zero capabilities. OWASP Rule #3.
Set --security-opt=no-new-privileges
Prevents setuid/setgid binaries inside the container from escalating privileges. A single flag that blocks a broad class of privilege escalation. OWASP Rule #4.
Never use --privileged
--privileged gives the container nearly all Linux capabilities and turns off seccomp and AppArmor profiles. It is equivalent to root on the host. Required only for very specific system containers.
Mount the filesystem read-only where possible
--read-only with --tmpfs=/tmp (for writable scratch space) prevents an attacker who gains code execution from persisting changes to the container filesystem. OWASP Rule #8.
Apply seccomp / AppArmor profiles
Docker's default seccomp profile blocks ~44 syscalls. Custom profiles restrict further. AppArmor profiles are the standard on Debian/Ubuntu. OWASP Rule #6.
Set resource limits (memory, CPU, pids)
Without limits, a single runaway container can exhaust all host resources. Add --memory=512m --cpus=0.5 --pids-limit=200 or equivalent deploy.resources in compose. OWASP Rule #7.
Run Docker in rootless mode
Rootless mode runs the daemon and containers as the invoking non-root user. This substantially reduces the blast radius of daemon compromise. OWASP Rule #11.
Network exposure
Bind published ports to 127.0.0.1, not 0.0.0.0
Docker's -p flag bypasses UFW. A port mapped as 8080:80 binds to 0.0.0.0 and is reachable from the internet regardless of your UFW rules. Use 127.0.0.1:8080:80 for internal services. OWASP Rule #5a.
Never expose the Docker daemon API on TCP without mTLS
Port 2375 (unencrypted) gives unauthenticated root-equivalent access to the entire host. If you need remote Docker access, use SSH tunnelling (DOCKER_HOST=ssh://user@host) rather than TCP. OWASP Rule #1.
Do not mount /var/run/docker.sock into containers
Mounting the Docker socket into a container is equivalent to giving that container full root access to the host. Any container that can speak to the socket can launch new containers, read secrets, and escape. OWASP Rule #1.
Use custom bridge networks; disable inter-container communication where not needed
Containers on the default bridge network can reach each other by IP. Create purpose-built networks so only containers that need to communicate are on the same network. OWASP Rule #5.
Secrets management
Use Docker secrets or a vault — not environment variables
Environment variables are visible in docker inspect, in /proc/<pid>/environ, and in many logging pipelines. Use Docker secrets (mounted at /run/secrets/<name>) or an external vault (HashiCorp Vault, AWS Secrets Manager) for production credentials.
Check MCP config files for plaintext API keys
Claude Code, Cursor, Windsurf, and other AI coding agents store MCP server configurations at well-known paths. These files routinely contain API keys and tokens in plaintext. Run keelix scan — it reads these paths and flags plaintext secrets.
Rotate credentials after any exposure event
If a secret was ever in a Docker image layer, an env var, or a public repository, treat it as compromised and rotate it immediately. Audit your git history with tools like git-secrets or truffleHog.
AI agents & MCP servers
No agent auto-approval on production boxes
Agents configured to approve all MCP tool calls without user confirmation turn a prompt-injection finding into a full blast-radius event. Verify no .mcp.json or agent config enables auto-approve in production.
Check for the lethal trifecta
If a deployed agent has access to private data, is exposed to untrusted input, and has a reachable exfiltration channel — all three present — it is a high-severity finding. Keelix checks all three legs deterministically.
Verify MCP servers against known publishers
Unvetted MCP servers with no verified publisher or recent package history are a supply-chain risk. Review your agent config before each deployment and cross-reference installed servers.
Host & supply chain
Keep the Docker Engine and host kernel patched
Container escapes and privilege escalation CVEs regularly target the kernel and the Docker Engine. Run unattended-upgrades (Ubuntu/Debian) or equivalent. OWASP Rule #0.
Use signed images and verify provenance
Enable Docker Content Trust (DOCKER_CONTENT_TRUST=1) or use cosign for image signing. Verify SBOMs for critical images. OWASP Rule #13.
Enable container activity logging at info level
The Docker daemon's default log level is 'info' — do not lower it to 'warning' or 'error'. Retain logs for audit and incident response. OWASP Rule #10.
Automate the whole checklist
Every CRIT and WARN item in this checklist is a deterministic check that Keelix runs automatically. Rather than working through it by hand before each deployment, you can run keelix scan on the box and get a 0–100 posture score that reflects all of them in a single pass.
The score is machine-readable: gate your CI pipeline on a threshold and the deployment is blocked if any CRIT finding appears — including exposed ports, the Docker socket mounted into a container, a non-root user misconfiguration, and any AI agent running with auto-approval enabled on the production box.
Keelix checks over 90 controls, including the full OWASP Docker Security Cheat Sheet and AonCyberLabs Secure Deployment Guidelines plus the AI/MCP surface (auto-approval, plaintext secrets in agent configs, the lethal trifecta) that no other pre-deploy checker covers.
- name: Keelix pre-deploy gate
uses: jakelamon/keelix@v0.1.0
with:
threshold: 80
fail-on: criticalExit code 0 means the posture score meets the threshold and no blocked findings triggered. Exit code 1 means something in this checklist failed — with the finding, severity, and control ID in the output.
The surface most checklists miss
Standard Docker security checklists — including the OWASP and AonCyberLabs references — were written before AI coding agents became a default part of the self-hosted developer stack. Section 05 of this checklist (AI agents & MCP servers) covers checks that no published Docker hardening guide currently includes.
If you run Claude Code, Cursor, Windsurf, or any MCP-enabled agent on the same box as your containers — and many self-hosted teams do — those agent configs are on the attack surface. A misconfigured agent with auto-approval enabled and a plaintext API key in its config file is a critical finding even if every other checklist item passes.
Frequently asked
- What should I check before deploying a Docker container to production?
- Before deploying, verify: (1) your image runs as a non-root user; (2) no secrets are baked into the image or passed via environment variables — use Docker secrets or a vault; (3) published ports bind to 127.0.0.1, not 0.0.0.0, unless you explicitly need public access; (4) the Docker daemon socket is not mounted into any container; (5) capabilities are dropped with --cap-drop=ALL and only the required ones are re-added; (6) --security-opt=no-new-privileges is set; and (7) any AI agents or MCP servers on the box have no auto-approval enabled and no plaintext credentials in their config files. Run keelix scan to verify all of the above in a single automated pass.
- What is a docker-compose security checklist?
- In docker-compose.yml, each service should include: read_only: true on the filesystem, security_opt: [no-new-privileges:true], cap_drop: [ALL] with only necessary capabilities re-added in cap_add, ports bound to 127.0.0.1 for internal services, and resource limits (deploy.resources.limits) to prevent denial-of-service. Secrets should use the top-level secrets: block referencing a mounted file or Docker Swarm secret — not environment variables. The OWASP Docker Security Cheat Sheet and Docker Docs both cover these patterns.
- How do I stop Docker from bypassing my firewall (UFW)?
- Docker inserts iptables rules directly into the DOCKER chain, which is evaluated before UFW's FORWARD rules. A UFW deny rule will not block a Docker-published port. The correct fix is to bind the host port to 127.0.0.1 in the -p mapping: -p 127.0.0.1:8080:80 instead of -p 8080:80. In docker-compose.yml: ports: ['127.0.0.1:8080:80']. Services that need to be publicly reachable should sit behind a reverse proxy (Nginx, Caddy, Traefik) — the proxy binds to the public interface; internal services bind to localhost only.
- How do I check if my Docker setup is secure?
- Run keelix scan on the host. It checks over 90 deterministic controls across image config, container runtime, host settings, network exposure, and AI/MCP agents — and produces a 0–100 posture score. For the subset of CIS Docker Benchmark checks, docker-bench-security (the official CIS reference tool) produces a pass/warn/fail log you can also review. Keelix automates both and adds the outside-in exposure check and AI/MCP surface that docker-bench-security does not cover.
Related
- Harden Docker — container hardening step by step
- Is my Docker exposed? — the UFW bypass and port 2375
- MCP Security — grade AI agents and MCP servers before they ship
- GitHub Action — gate CI on posture score
- Keelix vs Trivy — CVE scanning vs whole-box posture
- CIS Docker Benchmark scanner — scored, audit-ready reports
Run the whole checklist in under two minutes.
Free, local-first, Apache-2.0. Over 90 deterministic checks including the OWASP rules, CIS Benchmark controls, and the AI/MCP surface. Nothing leaves the box.