
Docker Core Security
- 9 installs
- 9 repo stars
- Updated July 8, 2026
- openaec-foundation/docker-claude-skill-package
Helps with security tasks.
About
docker-core-security is a Claude Code skill for security. It helps solo builders move faster with AI-assisted development.
- docker-core-security
- Security
- AI-coding skill
Docker Core Security by the numbers
- 9 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,675 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/docker-claude-skill-package --skill docker-core-securityAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 9 |
|---|---|
| repo stars | ★ 9 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/docker-claude-skill-package ↗ |
What it does
Helps with security tasks.
Files
docker-core-security
Quick Reference
Security Layers Overview
| Layer | Mechanism | Purpose |
|---|---|---|
| Image Supply Chain | Content trust, scanning, pinned digests | Verify image integrity and known vulnerabilities |
| Build-Time | Secrets mounts, multi-stage builds, .dockerignore | Prevent secrets leaking into image layers |
| Runtime Isolation | Namespaces, cgroups, seccomp, AppArmor | Kernel-level process and resource isolation |
| Least Privilege | Non-root USER, cap-drop ALL, read-only FS | Minimize attack surface inside the container |
| Resource Limits | Memory, CPU, PID limits | Prevent denial-of-service via resource exhaustion |
| Host Protection | Rootless Docker, no-new-privileges | Reduce daemon and container escape impact |
Minimum Viable Security (Quick-Start)
Apply these five settings to EVERY production container:
# Dockerfile
FROM node:20-alpine
RUN addgroup -g 1001 -S appgroup && adduser -u 1001 -S appuser -G appgroup
WORKDIR /app
COPY --chown=1001:1001 . .
USER 1001:1001
CMD ["node", "server.js"]# Runtime
docker run -d \
--read-only \
--tmpfs /tmp:size=64m \
--cap-drop ALL \
--security-opt no-new-privileges=true \
-m 512m --cpus 1.5 --pids-limit 200 \
myapp:v1# Docker Compose equivalent
services:
app:
image: myapp:v1
read_only: true
tmpfs:
- /tmp:size=64m
cap_drop:
- ALL
security_opt:
- no-new-privileges=true
deploy:
resources:
limits:
memory: 512m
cpus: "1.5"
pids: 200
user: "1001:1001"Critical Warnings
NEVER use --privileged in production -- it grants the container ALL host capabilities and access to ALL host devices. ALWAYS use specific --cap-add flags for the exact capabilities needed.
NEVER store secrets in Dockerfile ENV, ARG, or COPY instructions -- they persist in image layers and are visible via docker history. ALWAYS use --mount=type=secret for build-time secrets and Docker secrets or environment variables for runtime.
NEVER run containers as root unless there is a specific technical requirement -- most application workloads run correctly as non-root. ALWAYS add a USER instruction to your Dockerfile.
NEVER use latest tag in production -- it is mutable and prevents reproducible deployments. ALWAYS pin to a specific version tag or image digest.
NEVER expose the Docker daemon API over TCP without TLS -- API access equals root access on the host. ALWAYS use Unix socket (default) or SSH tunneling for remote access.
NEVER disable seccomp (--security-opt seccomp=unconfined) in production -- it removes syscall filtering that blocks container escapes. ALWAYS use the default seccomp profile or a custom restricted profile.
---
Security Hardening Decision Tree
Container needs hardening?
|
+-- Is the image from a trusted source?
| +-- NO --> Pin to verified digest: FROM image@sha256:...
| +-- YES --> Pin version tag, scan with Docker Scout
|
+-- Does the app need root?
| +-- NO --> Add USER 1001:1001 to Dockerfile
| +-- YES --> Document WHY, use --cap-drop ALL --cap-add <specific>
|
+-- Does the app write to the filesystem?
| +-- NO --> Use --read-only
| +-- YES to specific dirs --> Use --read-only --tmpfs /path
| +-- YES broadly --> Skip --read-only, log a security debt ticket
|
+-- Does the app need special kernel access?
| +-- NO --> Use --cap-drop ALL (add nothing)
| +-- YES --> --cap-drop ALL --cap-add <EXACT_CAP> (see capabilities table)
|
+-- Is this exposed to the internet?
| +-- YES --> Add resource limits, no-new-privileges, seccomp default
| +-- NO --> Still apply resource limits (defense in depth)---
Non-Root Containers
Dockerfile USER Instruction
ALWAYS use numeric UID/GID for deterministic behavior across environments:
# Alpine-based
RUN addgroup -g 1001 -S appgroup && adduser -u 1001 -S appuser -G appgroup
USER 1001:1001
# Debian/Ubuntu-based
RUN groupadd -r -g 1001 appgroup && useradd --no-log-init -r -u 1001 -g appgroup appuser
USER 1001:1001Runtime Override
docker run -u 1001:1001 nginx
docker run --user nobody nginxCompose
services:
app:
image: myapp:v1
user: "1001:1001"---
Linux Capabilities
Drop-All-Add-Specific Pattern
ALWAYS start with --cap-drop ALL and add back only what the application requires:
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginxCommon Capabilities Reference
| Capability | Purpose | When Needed |
|---|---|---|
NET_BIND_SERVICE | Bind to ports < 1024 | Web servers on port 80/443 |
CHOWN | Change file ownership | Apps managing file permissions |
SETUID / SETGID | Change process UID/GID | Apps switching users at runtime |
SYS_PTRACE | Process tracing | Debugging, profiling tools |
NET_ADMIN | Network configuration | VPN containers, network tools |
SYS_ADMIN | Mount operations, broad access | Avoid -- use specific caps instead |
DAC_OVERRIDE | Bypass file permission checks | Avoid -- fix file permissions instead |
Compose Syntax
services:
app:
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE---
Read-Only Root Filesystem
ALWAYS use --read-only with --tmpfs for directories that require writes:
docker run --read-only \
--tmpfs /tmp:size=64m \
--tmpfs /run:size=16m \
--tmpfs /var/cache/nginx:size=32m \
nginxCompose
services:
app:
read_only: true
tmpfs:
- /tmp:size=64m
- /run:size=16m---
Seccomp and AppArmor Profiles
Seccomp (Syscall Filtering)
Docker applies a default seccomp profile that blocks ~44 dangerous syscalls. ALWAYS keep this enabled.
# Default profile (applied automatically)
docker run --security-opt seccomp=default nginx
# Custom restrictive profile
docker run --security-opt seccomp=custom-profile.json nginxAppArmor (Mandatory Access Control)
# Default Docker AppArmor profile
docker run --security-opt apparmor=docker-default nginx
# Custom profile
docker run --security-opt apparmor=my-custom-profile nginxNo-New-Privileges
ALWAYS enable this to prevent processes inside the container from gaining additional privileges via setuid/setgid binaries:
docker run --security-opt no-new-privileges=true nginx---
Content Trust and Image Signing
DOCKER_CONTENT_TRUST
# Enable globally
export DOCKER_CONTENT_TRUST=1
# With content trust enabled:
docker pull nginx # Fails if image is not signed
docker push myapp:v1 # Automatically signs the imageDigest Pinning
ALWAYS pin production base images to a digest for supply chain security:
FROM alpine@sha256:c5b1261d6d3e43071626931fc004f70149baed4c52b3b3d4f8d72af0a7e2d708---
Image Scanning
Docker Scout
# Quick vulnerability overview
docker scout quickview myapp:v1
# Detailed CVE listing
docker scout cves myapp:v1
# Critical and high severity only
docker scout cves --only-severity critical,high myapp:v1
# Only fixable vulnerabilities
docker scout cves --only-fixed myapp:v1
# Compare versions for upgrade decisions
docker scout compare myapp:v1 --to myapp:v2
# Get base image upgrade recommendations
docker scout recommendations myapp:v1
# CI/CD gate (exit code 2 if vulnerabilities found)
docker scout cves -e myapp:v1Trivy
# Scan local image
trivy image myapp:v1
# Critical/high only
trivy image --severity CRITICAL,HIGH myapp:v1
# Exit code for CI (1 if vulns found)
trivy image --exit-code 1 --severity CRITICAL myapp:v1
# Scan filesystem
trivy fs .
# Generate SBOM
trivy image --format spdx-json -o sbom.json myapp:v1Snyk
# Scan image
snyk container test myapp:v1
# With Dockerfile for remediation advice
snyk container test myapp:v1 --file=Dockerfile
# Monitor for new vulnerabilities
snyk container monitor myapp:v1See references/scanning.md for complete scanning integration details.
---
Resource Limits (DoS Prevention)
ALWAYS set resource limits on production containers:
docker run -d \
-m 512m --memory-swap 1g \
--cpus 1.5 \
--pids-limit 200 \
--ulimit nofile=1024:2048 \
myapp:v1| Flag | Purpose | Recommended |
|---|---|---|
-m, --memory | Hard memory limit | Set based on application profiling |
--memory-swap | Memory + swap limit | Set to 2x memory or equal to prevent swap |
--cpus | CPU quota | Match to workload, start conservative |
--pids-limit | Max processes (fork bomb prevention) | 200 for most apps, 100 for simple services |
--ulimit nofile | File descriptor limit | 1024:2048 for most apps |
---
Rootless Docker
Runs BOTH the daemon and containers entirely without root privileges.
# Install (as non-root user)
dockerd-rootless-setuptool.sh install
# Configure environment
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
# Enable auto-start with lingering
sudo loginctl enable-linger $(whoami)
# Manage via systemd user units
systemctl --user start docker
systemctl --user enable dockerKey limitation: Rootless mode does NOT support AppArmor, overlay network drivers on older kernels, or --net=host on all configurations. ALWAYS test your workload in rootless mode before committing to it.
---
Build-Time Secret Management
NEVER bake secrets into image layers. ALWAYS use BuildKit secret mounts:
# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=aws_creds,target=/root/.aws/credentials \
aws s3 cp s3://bucket/file /app/filedocker buildx build --secret id=aws_creds,src=$HOME/.aws/credentials .SSH Agent Forwarding
docker buildx build --ssh default=$SSH_AUTH_SOCK .RUN --mount=type=ssh git clone git@github.com:org/private-repo.git---
Docker Bench for Security
Automated audit against CIS Docker Benchmark:
docker run --rm --net host --pid host \
--userns host --cap-add audit_control \
-e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
-v /var/lib:/var/lib:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v /usr/lib/systemd:/usr/lib/systemd:ro \
-v /etc:/etc:ro \
docker/docker-bench-securityALWAYS run Docker Bench before deploying to production. Address all WARN findings in sections 1-5.
---
Supply Chain Security
SBOM (Software Bill of Materials)
# Generate SBOM during build
docker buildx build --sbom=true --push -t myapp:v1 .
# Scan existing image SBOM
docker scout sbom myapp:v1Provenance Attestations
# Enable SLSA provenance
docker buildx build --provenance=mode=max --push -t myapp:v1 .---
Reference Links
- references/scanning.md -- Docker Scout, Trivy, Snyk integration and CI/CD pipeline patterns
- references/hardening-checklist.md -- Complete security hardening checklist for audits
- references/anti-patterns.md -- Security anti-patterns with exploit scenarios
Official Sources
- https://docs.docker.com/engine/security/
- https://docs.docker.com/engine/security/rootless/
- https://docs.docker.com/scout/
- https://docs.docker.com/reference/cli/docker/scout/cves/
- https://docs.docker.com/build/building/best-practices/
- https://docs.docker.com/engine/security/seccomp/
- https://docs.docker.com/engine/security/apparmor/
Docker Security Anti-Patterns
Each anti-pattern includes: the mistake, why it is dangerous, and the correct alternative.
---
AP-01: Running as Root by Default
Anti-pattern:
FROM node:20
WORKDIR /app
COPY . .
RUN npm ci
CMD ["node", "server.js"]
# No USER instruction -- runs as root (UID 0)Why dangerous: If an attacker exploits a vulnerability in the application, they gain root access inside the container. Combined with a container escape vulnerability, this means root on the host.
Correct:
FROM node:20-alpine
WORKDIR /app
COPY --chown=1001:1001 . .
RUN addgroup -g 1001 -S appgroup && adduser -u 1001 -S appuser -G appgroup
RUN npm ci --production
USER 1001:1001
CMD ["node", "server.js"]---
AP-02: Secrets in Image Layers
Anti-pattern:
# Secrets persist in image history even if deleted later
COPY credentials.json /app/
RUN ./setup.sh --config /app/credentials.json
RUN rm /app/credentials.json # Still in previous layer!# Secrets visible via docker inspect
ENV API_KEY=sk-live-abc123def456
ARG DB_PASSWORD=mysecretpasswordWhy dangerous: Image layers are immutable. Anyone with docker pull access can run docker history --no-trunc to see ENV/ARG values or extract deleted files from earlier layers.
Correct:
# syntax=docker/dockerfile:1
# Build-time secret (not stored in any layer)
RUN --mount=type=secret,id=api_key \
cat /run/secrets/api_key | ./setup.sh --api-key-stdin
# Runtime: pass via environment variable or Docker secret
# docker run -e API_KEY="$(cat ~/.api_key)" myapp---
AP-03: Using --privileged
Anti-pattern:
# "It works with --privileged" is not a valid solution
docker run --privileged myappWhy dangerous: --privileged grants ALL Linux capabilities, access to ALL host devices (/dev/*), and disables seccomp, AppArmor, and SELinux. The container effectively has full root access to the host kernel.
Correct:
# Identify the exact capability needed and grant only that
docker run --cap-drop ALL --cap-add SYS_PTRACE myapp
# If device access is needed, mount the specific device
docker run --device /dev/snd myapp---
AP-04: Using :latest Tag in Production
Anti-pattern:
FROM python:latestservices:
app:
image: nginx:latestWhy dangerous: :latest is a mutable tag. It points to different images over time. A docker pull on Monday and Tuesday may yield different images with different vulnerabilities or breaking changes. Builds are not reproducible.
Correct:
# Pin to specific version
FROM python:3.12.1-slim
# Best: pin to digest for immutable reference
FROM python@sha256:abc123def456...---
AP-05: Exposing Docker Daemon Over TCP Without TLS
Anti-pattern:
// daemon.json
{
"hosts": ["tcp://0.0.0.0:2375"]
}Why dangerous: The Docker API is equivalent to root access on the host. Anyone who can reach port 2375 can create privileged containers, mount the host filesystem, or deploy cryptocurrency miners. This is actively scanned for by botnets.
Correct:
# Use SSH tunneling (recommended)
export DOCKER_HOST=ssh://user@remote-host
# Or TLS with client certificates
dockerd --tlsverify --tlscacert=ca.pem --tlscert=server-cert.pem --tlskey=server-key.pem -H=0.0.0.0:2376---
AP-06: No Resource Limits
Anti-pattern:
# No memory, CPU, or PID limits
docker run -d myappWhy dangerous: A single container can consume all host memory (triggering OOM kills of other containers), saturate all CPU cores, or spawn unlimited processes (fork bomb). This is a denial-of-service risk for all containers on the host.
Correct:
docker run -d \
-m 512m --memory-swap 1g \
--cpus 1.5 \
--pids-limit 200 \
myapp---
AP-07: Using Default Bridge Network
Anti-pattern:
# Containers on default bridge cannot resolve each other by name
docker run -d --name db postgres
docker run -d --name app --link db:db myapp # --link is legacyWhy dangerous: The default bridge network lacks DNS resolution, provides no isolation (all containers can communicate), and cannot be configured per-network. The --link flag is legacy and deprecated.
Correct:
docker network create myapp-net
docker run -d --name db --network myapp-net postgres
docker run -d --name app --network myapp-net myapp
# app can reach db at hostname "db" via Docker DNS---
AP-08: Disabling Seccomp
Anti-pattern:
docker run --security-opt seccomp=unconfined myappWhy dangerous: The default seccomp profile blocks approximately 44 dangerous syscalls including mount, reboot, keyctl, and ptrace (in some modes). Disabling it removes a critical layer of defense that prevents container escape exploits.
Correct:
# Use default profile (applied automatically, just don't disable it)
docker run myapp
# Or create a custom profile that restricts further
docker run --security-opt seccomp=custom-restrictive.json myapp---
AP-09: Mounting Docker Socket into Containers
Anti-pattern:
docker run -v /var/run/docker.sock:/var/run/docker.sock myappWhy dangerous: Any process in the container can use the Docker socket to create new privileged containers, mount the host filesystem, or execute commands as root on the host. This is equivalent to giving the container full root access to the host.
Correct:
# If Docker API access is genuinely needed (CI/CD, monitoring):
# 1. Use a socket proxy that filters allowed API calls
docker run -v /var/run/docker.sock:/var/run/docker-proxy.sock:ro \
tecnativa/docker-socket-proxy
# 2. Or use rootless Docker where the socket has limited impact
# 3. Or use a dedicated CI runner with minimal host access---
AP-10: Ignoring .dockerignore
Anti-pattern:
COPY . .
# Copies everything: .git, .env, node_modules, credentials, SSH keysWithout a .dockerignore, the build context includes all files in the directory, including secrets and unnecessary bulk.
Why dangerous: Credentials, environment files, and SSH keys get baked into image layers. Large directories like .git and node_modules bloat the image and build context.
Correct:
# .dockerignore
.git
.env
.env.*
*.pem
*.key
credentials.*
node_modules
__pycache__
.DS_Store
Dockerfile
docker-compose*.yml---
AP-11: Writable Root Filesystem
Anti-pattern:
# Default: container filesystem is writable
docker run -d myappWhy dangerous: An attacker who compromises the application can write malicious binaries, modify configuration, or tamper with system files inside the container.
Correct:
docker run -d --read-only \
--tmpfs /tmp:size=64m \
--tmpfs /var/run:size=16m \
myapp---
AP-12: Not Scanning Images
Anti-pattern:
# Build and deploy without any vulnerability check
docker build -t myapp:v1 .
docker push registry.example.com/myapp:v1Why dangerous: Known CVEs in base images and dependencies go undetected. An image may ship with critical vulnerabilities that have known exploits and available fixes.
Correct:
# Scan as part of CI/CD pipeline
docker build -t myapp:v1 .
docker scout cves --only-severity critical,high --only-fixed -e myapp:v1
# Pipeline fails if fixable critical/high CVEs exist---
Summary Table
| # | Anti-Pattern | Risk Level | Quick Fix |
|---|---|---|---|
| AP-01 | Running as root | Critical | Add USER 1001:1001 |
| AP-02 | Secrets in layers | Critical | Use --mount=type=secret |
| AP-03 | --privileged | Critical | --cap-drop ALL --cap-add <specific> |
| AP-04 | :latest tag | High | Pin version or digest |
| AP-05 | TCP daemon without TLS | Critical | SSH tunnel or TLS certs |
| AP-06 | No resource limits | High | Add -m, --cpus, --pids-limit |
| AP-07 | Default bridge | Medium | User-defined bridge network |
| AP-08 | Seccomp disabled | High | Keep default profile |
| AP-09 | Docker socket mount | Critical | Socket proxy or rootless |
| AP-10 | No .dockerignore | High | Create comprehensive .dockerignore |
| AP-11 | Writable root FS | Medium | --read-only + --tmpfs |
| AP-12 | No image scanning | High | Docker Scout or Trivy in CI/CD |
Docker Security Hardening Checklist
Use this checklist before deploying containers to production. Items are ordered by priority.
---
1. Image Supply Chain
- [ ] Base image pinned to digest or specific version tag -- NEVER use
:latestin production
FROM node:20.11-alpine@sha256:abc123...- [ ] Base image is an official or verified publisher image -- ALWAYS prefer Docker Official Images
- [ ] Image scanned for vulnerabilities -- Run
docker scout cves --only-severity critical,highortrivy image --severity CRITICAL,HIGH - [ ] No critical/high fixable vulnerabilities -- Address all fixable CVEs before deployment
- [ ] SBOM generated and stored --
docker buildx build --sbom=trueortrivy image --format spdx-json - [ ] Content trust enabled for pulls --
export DOCKER_CONTENT_TRUST=1 - [ ] Provenance attestation enabled --
docker buildx build --provenance=mode=max - [ ] Minimal base image used -- Alpine (<6 MB) or distroless for production
---
2. Build-Time Security
- [ ] No secrets in image layers -- NEVER use
ENV,ARG, orCOPYfor secrets
# CORRECT
RUN --mount=type=secret,id=api_key cat /run/secrets/api_key
# WRONG
ENV API_KEY=supersecret- [ ] Multi-stage build used -- Build tools and dependencies NOT present in final image
- [ ] `.dockerignore` configured -- Excludes
.git,.env,node_modules, credentials files - [ ] Package manager cache cleaned --
rm -rf /var/lib/apt/lists/*after install - [ ] SSH agent forwarding for private repos --
--mount=type=sshinstead of copying keys - [ ] Build arguments validated -- No sensitive defaults in
ARGinstructions
---
3. Runtime User
- [ ] Container runs as non-root --
USER 1001:1001in Dockerfile
RUN addgroup -g 1001 -S appgroup && adduser -u 1001 -S appuser -G appgroup
USER 1001:1001- [ ] Numeric UID/GID used -- ALWAYS use numeric IDs, not names (deterministic across images)
- [ ] File ownership set --
COPY --chown=1001:1001orRUN chownbeforeUSERinstruction - [ ] No setuid/setgid binaries -- Remove or verify necessity:
find / -perm /6000 -type f
---
4. Linux Capabilities
- [ ] ALL capabilities dropped --
--cap-drop ALLALWAYS as the starting point - [ ] Only required capabilities added --
--cap-add NET_BIND_SERVICE(document each addition) - [ ] `--privileged` NOT used -- NEVER in production, use specific
--cap-addinstead - [ ] No-new-privileges enabled --
--security-opt no-new-privileges=true
Compose equivalent:
services:
app:
cap_drop: [ALL]
cap_add: [NET_BIND_SERVICE]
security_opt: [no-new-privileges=true]---
5. Filesystem
- [ ] Read-only root filesystem --
--read-only - [ ] Writable dirs via tmpfs --
--tmpfs /tmp:size=64mfor each required writable path - [ ] No host filesystem mounts in production -- Avoid bind mounts to sensitive host paths
- [ ] Volumes use named volumes -- NEVER anonymous volumes for persistent data
---
6. Network
- [ ] User-defined bridge network -- NEVER use the default bridge
- [ ] Minimal port exposure -- Only publish ports that external clients need
- [ ] Internal networks for backend services --
docker network create --internal backend - [ ] No `--network host` -- Unless required for performance (document the reason)
- [ ] DNS configured -- Custom DNS if needed, not relying on host resolv.conf
---
7. Resource Limits
- [ ] Memory limit set --
-m 512m(based on application profiling) - [ ] Memory-swap limit set --
--memory-swapequal to or 2x memory limit - [ ] CPU limit set --
--cpus 1.5(based on workload requirements) - [ ] PID limit set --
--pids-limit 200(fork bomb prevention) - [ ] File descriptor limit set --
--ulimit nofile=1024:2048
Compose equivalent:
services:
app:
deploy:
resources:
limits:
memory: 512m
cpus: "1.5"
pids: 200
ulimits:
nofile:
soft: 1024
hard: 2048---
8. Seccomp and AppArmor
- [ ] Default seccomp profile active -- Do NOT disable with
seccomp=unconfined - [ ] Custom seccomp profile for sensitive workloads -- Restrict to exact needed syscalls
- [ ] AppArmor profile active (Linux only) -- Default
docker-defaultor custom profile - [ ] SELinux labels set (if applicable) --
--security-opt label=type:svirt_apache_t
---
9. Daemon Security
- [ ] Docker daemon not exposed over TCP -- Use Unix socket or SSH tunneling
- [ ] TLS enabled if remote API is required --
--tlsverify --tlscacert --tlscert --tlskey - [ ] Docker group membership restricted -- Only trusted users in the
dockergroup - [ ] Rootless Docker considered -- Evaluate for environments where daemon-level isolation matters
- [ ] Logging configured --
--log-driver json-file --log-opt max-size=10m --log-opt max-file=3
---
10. Monitoring and Auditing
- [ ] Docker Bench for Security run -- Address all WARN findings in sections 1-5
docker run --rm --net host --pid host --userns host --cap-add audit_control \
-v /var/lib:/var/lib:ro -v /var/run/docker.sock:/var/run/docker.sock:ro \
-v /usr/lib/systemd:/usr/lib/systemd:ro -v /etc:/etc:ro \
docker/docker-bench-security- [ ] Health checks configured --
HEALTHCHECKin Dockerfile or--health-cmdat runtime - [ ] Container resource usage monitored --
docker statsor Prometheus/Grafana - [ ] Image scanning in CI/CD pipeline -- Automated on every build
- [ ] Regular re-scanning of deployed images -- Weekly or on new CVE database updates
---
Quick Compliance Summary
| CIS Benchmark Area | Key Controls |
|---|---|
| 1 - Host Configuration | Rootless Docker, audit logging, separate partition for Docker |
| 2 - Docker Daemon | TLS, restricted API access, logging configured |
| 3 - Docker Daemon Config | user namespace remapping, seccomp, AppArmor |
| 4 - Container Images | No secrets in layers, non-root user, health checks |
| 5 - Container Runtime | Read-only FS, cap-drop ALL, resource limits, no-new-privileges |
| 6 - Docker Security Operations | Image scanning, bench audit, monitoring |
Image Scanning Reference
Docker Scout
Command Reference
| Command | Purpose |
|---|---|
docker scout quickview IMAGE | Summary of vulnerability counts by severity |
docker scout cves IMAGE | Detailed CVE listing with package info |
docker scout recommendations IMAGE | Base image upgrade suggestions |
docker scout compare IMAGE --to IMAGE | Diff vulnerabilities between two images |
docker scout sbom IMAGE | View Software Bill of Materials |
Filtering Flags
| Flag | Description | Example |
|---|---|---|
--only-severity | Filter by severity level | --only-severity critical,high |
--only-fixed | Only show fixable CVEs | --only-fixed |
--only-unfixed | Only show unfixable CVEs | --only-unfixed |
--only-cve-id | Target specific CVE IDs | --only-cve-id CVE-2024-1234 |
--only-cisa-kev | CISA Known Exploited Vulnerabilities | --only-cisa-kev |
--only-package-type | Filter by package manager | --only-package-type apk,npm |
--only-package | Regex match on package name | --only-package "openssl.*" |
--ignore-base | Exclude base image CVEs | --ignore-base |
--only-base | Only base image CVEs | --only-base |
--epss | Include EPSS exploit probability scores | --epss |
--epss-score | Filter by minimum EPSS score | --epss-score 0.5 |
--epss-percentile | Filter by EPSS percentile | --epss-percentile 0.9 |
--ignore-suppressed | Exclude Scout exceptions | --ignore-suppressed |
Artifact URI Prefixes
| Prefix | Source |
|---|---|
image:// | Local image with registry fallback (default) |
local:// | Local image store only |
registry:// | Registry only |
oci-dir:// | OCI layout directory |
archive:// | Docker save tarball |
fs:// | Local directory or file |
sbom:// | SPDX/in-toto/syft JSON SBOM |
Output Formats
| Flag | Format | Use Case |
|---|---|---|
--format sarif | SARIF JSON | IDE integration, GitHub Code Scanning |
--format markdown | Markdown table | Documentation, PR comments |
--format spdx | SPDX JSON | SBOM exchange |
--format json | Raw JSON | Custom tooling |
# Generate SARIF for GitHub Code Scanning
docker scout cves --format sarif -o scout-report.sarif myapp:v1
# Generate markdown for PR comments
docker scout cves --format markdown -o report.md myapp:v1CI/CD Integration Pattern
#!/bin/bash
# CI vulnerability gate script
set -e
IMAGE="${1:?Usage: scan.sh IMAGE:TAG}"
echo "=== Scanning $IMAGE for vulnerabilities ==="
# Quick overview
docker scout quickview "$IMAGE"
# Fail pipeline on critical/high fixable vulnerabilities
docker scout cves -e --only-severity critical,high --only-fixed "$IMAGE"
EXIT_CODE=$?
if [ $EXIT_CODE -eq 2 ]; then
echo "FAIL: Fixable critical/high vulnerabilities found"
docker scout recommendations "$IMAGE"
exit 1
fi
echo "PASS: No fixable critical/high vulnerabilities"GitHub Actions Integration
name: Docker Scout Scan
on:
push:
branches: [main]
pull_request:
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
context: .
load: true
tags: myapp:scan
- uses: docker/scout-action@v1
with:
command: cves
image: myapp:scan
only-severities: critical,high
only-fixed: true
exit-code: true---
Trivy
Command Reference
| Command | Purpose |
|---|---|
trivy image IMAGE | Scan container image |
trivy fs PATH | Scan filesystem / source code |
trivy config PATH | Scan IaC files (Dockerfile, Compose, K8s) |
trivy sbom IMAGE | Generate SBOM |
trivy repo URL | Scan Git repository |
Common Flags
| Flag | Description | Example |
|---|---|---|
--severity | Filter by severity | --severity CRITICAL,HIGH |
--exit-code | Exit code when vulns found | --exit-code 1 |
--ignore-unfixed | Skip unfixable CVEs | --ignore-unfixed |
--format | Output format | --format json, --format sarif, --format table |
-o | Output file | -o report.json |
--timeout | Scan timeout | --timeout 10m |
--skip-dirs | Directories to skip | --skip-dirs node_modules |
--skip-files | Files to skip | --skip-files package-lock.json |
Trivy CI/CD Pattern
#!/bin/bash
set -e
IMAGE="${1:?Usage: trivy-scan.sh IMAGE:TAG}"
# Scan for critical vulnerabilities, fail if found
trivy image \
--exit-code 1 \
--severity CRITICAL \
--ignore-unfixed \
--format table \
"$IMAGE"
echo "PASS: No critical fixable vulnerabilities"Trivy GitHub Actions
- name: Trivy Scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:v1
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH
exit-code: 1
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarifTrivy Dockerfile/Compose Scanning
# Scan Dockerfile for misconfigurations
trivy config Dockerfile
# Scan docker-compose.yml
trivy config docker-compose.yml
# Scan entire project IaC
trivy config .---
Snyk Container
Command Reference
| Command | Purpose |
|---|---|
snyk container test IMAGE | One-time vulnerability scan |
snyk container monitor IMAGE | Continuous monitoring |
snyk container test IMAGE --file=Dockerfile | Scan with remediation advice |
Snyk CI/CD Pattern
#!/bin/bash
set -e
IMAGE="${1:?Usage: snyk-scan.sh IMAGE:TAG}"
# Test with Dockerfile context for remediation
snyk container test "$IMAGE" \
--file=Dockerfile \
--severity-threshold=high
echo "PASS: No high/critical vulnerabilities"---
Scanning Strategy
When to Use Each Scanner
| Scanner | Best For | Cost |
|---|---|---|
| Docker Scout | Docker Hub images, quick triage, base image recommendations | Free tier available |
| Trivy | CI/CD pipelines, IaC scanning, SBOM generation, air-gapped environments | Free / open source |
| Snyk | Enterprise workflows, continuous monitoring, developer-first remediation | Free tier + paid |
Recommended Pipeline
1. Build stage: Trivy scans Dockerfile for misconfigurations (trivy config) 2. Post-build: Docker Scout or Trivy scans built image for CVEs 3. Registry: Continuous monitoring via Snyk or registry-native scanning 4. Runtime: Periodic re-scanning of deployed images for new CVEs
SBOM Generation
ALWAYS generate an SBOM for production images:
# Via BuildKit (at build time)
docker buildx build --sbom=true --push -t myapp:v1 .
# Via Trivy (post-build)
trivy image --format spdx-json -o sbom.json myapp:v1
# Via Docker Scout
docker scout sbom --format spdx myapp:v1