| name | docker-security |
| description | Container hardening, image security, and Docker best practices. Use when building, scanning, or securing containers and images. |
| paths | **/Dockerfile*,**/docker-compose*,**/.dockerignore,**/docker-compose.yml,**/docker-compose.yaml,**/docker-bake* |
Docker Security
Image Build Security
- Lint before build:
hadolint Dockerfile
- Scan immediately:
trivy image <org>/<app>:<tag> — catch CVEs before registry push
- Generate SBOM:
syft <org>/<app>:<tag> -o spdx-json > sbom.spdx.json
- Check layers:
dive <org>/<app>:<tag> — identify bloat, leaked files
Image Hardening Checklist
- Base image: Use distroless or Chainguard —
gcr.io/distroless/base:nonroot or cgr.io/chainguard/base:latest
- No root:
RUN useradd -m -u 1000 app && chown -R app:app /app → USER app
- Read-only FS:
docker run --read-only --tmpfs /tmp --tmpfs /run <image>
- Drop caps:
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE <image>
- No secrets in layers: Use
RUN --mount=type=secret (Docker 18.04+)
Docker Compose Security
services:
app:
image: <image>:<pinned-tag>
user: "1000:1000"
read_only: true
cap_drop: [ALL]
cap_add: [NET_BIND_SERVICE]
tmpfs: [/tmp, /run]
security_opt:
- no-new-privileges:true
networks:
- internal
networks:
internal:
driver: bridge
driver_opts:
com.docker.network.bridge.enable_icc: "false"
Secret Scanning
trivy image --scan-secrets <org>/<app>:<tag>
grype <org>/<app>:<tag>
gitleaks detect .
Registry Hardening
cosign sign <image>:<tag>
cosign verify --key cosign.pub <image>:<tag>
trivy image --severity HIGH,CRITICAL --exit-code 1 <image>:<tag>
crane manifest <image>:<tag> | jq .
cosign tree <image>:<tag>
Runtime Security
docker run --security-opt apparmor=docker-default <image>
docker run -m 512m --cpus=1 <image>
docker run -v /data:/data:ro <image>
docker run --security-opt no-new-privileges:true <image>
Multi-Stage Build (Minimal Attack Surface)
# Stage 1: Build
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp .
# Stage 2: Runtime (distroless — no shell, no package manager)
FROM gcr.io/distroless/base:nonroot
COPY --from=builder /app/myapp /myapp
USER nonroot
ENTRYPOINT ["/myapp"]
Rules
- NEVER pin
latest tag in deployments — use commit SHA or semver
- NEVER copy entire filesystem — COPY selectively + maintain
.dockerignore
- NEVER log secrets to stdout/stderr (ends up in container logs)
- ALWAYS group
apt-get install && apt-get clean in single RUN layer
- ALWAYS use multi-stage builds to minimize final image size
- ALWAYS scan images in CI before push (
trivy --exit-code 1 on CRITICAL)
- NEVER run containers with
--privileged unless absolutely necessary