Claude Code Plugins

Community-maintained marketplace

Feedback

Docker/container security audit patterns. Load when Dockerfile or docker-compose.yml present. Covers secrets in layers, port exposure, non-root users, multi-stage builds, and compose security.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name security-docker
description Docker/container security audit patterns. Load when Dockerfile or docker-compose.yml present. Covers secrets in layers, port exposure, non-root users, multi-stage builds, and compose security.

Security audit patterns for Docker and container deployments covering secrets in images, port exposure, user privileges, and compose security.

Secrets in Images (Critical)

Secrets in Build Args/ENV

# ❌ CRITICAL: Secret in ENV (visible in image history)
ENV API_KEY=sk_live_abc123
ENV DATABASE_URL=postgres://user:password@host/db

# ❌ CRITICAL: Secret in ARG (visible in image history)
ARG AWS_SECRET_ACCESS_KEY
RUN aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY

# ✓ Use runtime secrets
# Pass via docker run -e or docker-compose environment/env_file

# ✓ Docker secrets (Swarm) or orchestrator-specific secrets
# Use /run/secrets/* instead of ENV/ARG when available

Secrets Baked into Layers

# ❌ CRITICAL: Even if deleted, secret is in layer history
COPY .env /app/.env
RUN source /app/.env && do_something
RUN rm /app/.env  # Still in previous layer!

# ❌ CRITICAL: Copying all files includes secrets
COPY . /app/  # Copies .env, .git, etc.

# ✓ Use .dockerignore
# In .dockerignore:
# .env*
# .git
# *.pem
# *.key

# ✓ Or explicit COPY
COPY package*.json /app/
COPY src/ /app/src/

Checking Image History

# Audit existing images for secrets
docker history --no-trunc <image>
docker inspect <image> | jq '.[0].Config.Env'

Port Exposure

docker-compose.yml

# ❌ CRITICAL: Database exposed to host network
services:
  db:
    image: postgres
    ports:
      - "5432:5432"  # Accessible from outside!

# ❌ CRITICAL: Redis without password
  redis:
    image: redis
    ports:
      - "6379:6379"  # And no AUTH!

# ✓ Internal only (accessible to other containers)
services:
  db:
    image: postgres
    expose:
      - "5432"  # Only internal
    # No 'ports' = not exposed to host

# ✓ If must expose, bind to localhost
  db:
    ports:
      - "127.0.0.1:5432:5432"  # Only localhost

Default Credentials

# ❌ No password or default password
services:
  db:
    image: postgres
    environment:
      POSTGRES_PASSWORD: postgres  # Default!

  redis:
    image: redis
    # No password at all

# ✓ Strong passwords from secrets
services:
  db:
    image: postgres
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt  # MUST NOT be in git!

Non-Root User

# ❌ Running as root (default)
FROM node:18
COPY . /app
CMD ["node", "server.js"]  # Runs as root

# ✓ Create and use non-root user
FROM node:18
WORKDIR /app
COPY --chown=node:node . .
USER node
CMD ["node", "server.js"]

# ✓ Using numeric UID (more portable)
FROM node:18
RUN useradd -r -u 1001 appuser
WORKDIR /app
COPY --chown=1001:1001 . .
USER 1001
CMD ["node", "server.js"]

Multi-Stage Builds

# ❌ Build tools and secrets in final image
FROM node:18
COPY . .
RUN npm install
RUN npm run build
CMD ["node", "dist/server.js"]
# Final image has: source, node_modules (dev deps), build tools

# ✓ Multi-stage: only production artifacts
FROM node:18 AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build

FROM node:18-slim AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/server.js"]
# Final image: minimal, no source, no build tools

Docker Compose Security

Privileged Mode

# ❌ CRITICAL: Full host access
services:
  app:
    privileged: true  # Container can do anything on host!

# ❌ HIGH: Dangerous capabilities
services:
  app:
    cap_add:
      - SYS_ADMIN
      - NET_ADMIN

Volume Mounts

# ❌ CRITICAL: Docker socket access = root on host
services:
  app:
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

# ❌ HIGH: Sensitive host paths
services:
  app:
    volumes:
      - /etc:/etc
      - /root:/root

Network Mode

# ❌ HIGH: Host network mode
services:
  app:
    network_mode: host  # Bypasses Docker network isolation

Image Security

Base Image

# ❌ Outdated or unverified
FROM node:14  # EOL version
FROM random-user/node-app  # Unverified

# ✓ Official, recent, minimal
FROM node:20-slim
FROM node:20-alpine

Image Scanning

# Scan for vulnerabilities
docker scout cves <image>
trivy image <image>
grype <image>

Quick Audit Commands

# Find secrets in Dockerfile
rg "(ENV|ARG).*(KEY|SECRET|PASSWORD|TOKEN)" Dockerfile*

# Find exposed ports in compose
rg "ports:" docker-compose*.yml -A 3

# Check for privileged/capabilities
rg "(privileged|cap_add|network_mode)" docker-compose*.yml

# Check for docker.sock mount
rg "docker.sock" docker-compose*.yml

# Check for USER instruction
grep "^USER" Dockerfile

# Check .dockerignore exists and has secrets
cat .dockerignore | grep -E "(env|key|secret|pem)"

Hardening Checklist

  • No secrets in ENV/ARG instructions
  • No secrets COPY'd into image
  • .dockerignore excludes .env, .git, *.pem, *.key
  • Database/Redis ports not exposed to host (or only 127.0.0.1)
  • Strong passwords for all services (not defaults)
  • USER instruction sets non-root user
  • Multi-stage build for production images
  • No privileged: true
  • No docker.sock mount (unless required)
  • Base images are official and recent
  • Images scanned for vulnerabilities