container-security-hardening — quality + safety report
In the Skillier index (antigravity__container-security-hardening) · scanned 2026-06-03 · engine: builtin+triage
3 heuristic flags to review
Heuristic flags from the builtin scanner, which is known to over-flag (it trips on legitimate env-reading integrations, security skills, and library .eval calls). This is NOT an authoritative malicious verdict — re-scan with SkillSpector for the authoritative result. Run the authoritative scan →
📇 This skill is in the Skillier index (curated · deduped · quality-filtered). Install Skillier to route & load it into your AI client.
Quality notes
About this skill
Harden Docker/container images and runtime deployments with secure base images, non-root users, CVE scanning, SBOM/signing, seccomp/AppArmor, and Kubernetes pod security controls. Use for Dockerfile security reviews, container CVEs, image scanning, distroless images, or production hardening.
📄 Read the SKILL.md
---
name: container-security-hardening
description: >
Harden Docker/container images and runtime deployments with secure base images,
non-root users, CVE scanning, SBOM/signing, seccomp/AppArmor, and Kubernetes
pod security controls. Use for Dockerfile security reviews, container CVEs,
image scanning, distroless images, or production hardening.
category: security
risk: safe
source: community
date_added: "2026-05-30"
---
# Container Security Hardening Skill
A production-focused guide for building, scanning, and running containers securely — from Dockerfile authoring through runtime enforcement and supply chain integrity.
---
## When to Use This Skill
- User mentions Docker security, container hardening, or Dockerfile security review
- User asks about distroless images, non-root containers, or read-only filesystems
- User wants to scan images for CVEs with Trivy, Grype, or Snyk
- User mentions seccomp, AppArmor, Linux capabilities, or runtime security
- User asks "is my Dockerfile secure?" or "how do I reduce my image attack surface?"
- User wants to sign/verify images with Cosign or generate SBOMs
- User asks about Kubernetes pod security, NetworkPolicy, or RBAC hardening
- User says "fix container CVEs" or "harden my container for production"
## When NOT to Use This Skill
- The user is primarily asking about GitHub Actions CI/CD → recommend `github-actions-advanced`
- The user needs general Docker usage help (not security) → recommend `docker-expert`
- The user is working with Kubernetes orchestration beyond security → recommend `kubernetes-architect`
- The user needs application-level security (SQL injection, XSS) → recommend `api-security-best-practices`
---
## Step 1: Understand Context Before Responding
When invoked, first detect the current state:
```bash
# Find Dockerfiles in the project
find . -name "Dockerfile*" -not -path "*/node_modules/*" | head -10
# Check for existing security tooling
ls .trivyignore .hadolint.yaml .snyk docker-compose*.yml 2>/dev/null
# Inspect base images currently in use
grep -r "^FROM" $(find . -name "Dockerfile*") 2>/dev/null
# Check if Kubernetes manifests exist
find . -name "*.yaml" -path "*/k8s/*" -o -name "*.yaml" -path "*/manifests/*" | head -10
```
Then adapt recommendations to:
- The tech stack (Node, Python, Go, Java — affects base image choice)
- Whether this is Docker-only or Kubernetes-deployed
- The CI platform in use (for scanner integration)
- The existing base images and how far they are from best practice
---
## The Five Layers of Container Security
```
1. Image Build → Minimal base, no secrets, non-root, read-only FS
2. Image Scanning → CVE scanning, SBOM, secret detection, Dockerfile lint
3. Runtime Security → Capabilities, seccomp, AppArmor, resource limits
4. Supply Chain → Signed images, pinned digests, trusted registries
5. Kubernetes Layer → Pod Security Admission, NetworkPolicy, RBAC, Kyverno
```
> Work through layers in order — hardening the image first gives the most leverage.
> See `references/base-image-comparison.md` for a full size/CVE trade-off table.
---
## Layer 1: Dockerfile Hardening
### 1.1 Use a Minimal Base Image
```dockerfile
# ❌ AVOID — massive attack surface (~100–200 CVEs typical)
FROM ubuntu:latest
FROM node:20
# ✅ BETTER — slim variants (glibc, smaller apt footprint)
FROM node:20-slim
FROM python:3.12-slim
# ✅ BEST — distroless (no shell, no package manager, built-in nonroot user)
FROM gcr.io/distroless/nodejs20-debian12
FROM gcr.io/distroless/python3-debian12
FROM gcr.io/distroless/static-debian12 # Go/Rust fully-static binaries
# ✅ ALSO GREAT — Alpine (musl libc; verify app compatibility first)
FROM alpine:3.20
# ✅ ZERO ATTACK SURFACE — for fully static binaries only
FROM scratch
```
See `references/base-image-comparison.md` for the full trade-off matrix.
### 1.2 Multi-Stage Build — Separate Build from Runtime
Never ship build tools, compilers, or dev dependencies in a production image.
```dockerfile
# syntax=docker/dockerfile:1
# ── Stage 1: Install & Build ──────────────────────────────
FROM node:20-slim AS builder
WORKDIR /build
COPY package*.json ./
RUN npm ci # Install all deps (including devDeps)
COPY . .
RUN npm run build && npm prune --production
# ── Stage 2: Runtime — minimal, no build tools ────────────
FROM gcr.io/distroless/nodejs20-debian12@sha256:<digest>
LABEL org.opencontainers.image.source="https://github.com/org/repo"
LABEL org.opencontainers.image.revision="${BUILD_SHA}"
LABEL org.opencontainers.image.licenses="MIT"
WORKDIR /app
COPY --from=builder --chown=nonroot:nonroot /build/dist ./dist
COPY --from=builder --chown=nonroot:nonroot /build/node_modules ./node_modules
USER nonroot:nonroot # UID 65532 — built into distroless
EXPOSE 3000
CMD ["dist/server.js"]
```
**Go / Rust static binary pattern:**
```dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /build
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o app .
FROM scratch # Zero attack surface
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /build/app /app
USER 65532:65532
ENTRYPOINT ["/app"]
```
### 1.3 Run as Non-Root User
```dockerfile
# For debian/ubuntu-based images — create dedicated user
RUN groupadd -r appgroup --gid 10001 && \
useradd -r -g appgroup --uid 10001 --no-log-init appuser
COPY --chown=appuser:appgroup . /app
USER appuser # Switch before CMD/ENTRYPOINT — never run as root
# ─────────────────────────────────────────────────────────
# For Alpine-based images
RUN addgroup -g 10001 -S appgroup && \
adduser -u 10001 -S appuser -G appgroup
# For distroless — nonroot (UID 65532) is already built in
USER nonroot:nonroot
```
### 1.4 Pin Base Images to Digest
```dockerfile
# ❌ UNSAFE — tags are mutable; image can be silently overwritten (supply chain attack)
FROM node:20-slim
# ✅ SAFE — SHA256 digest is cryptographically immutable
FROM node:20-slim@sha256:a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789ab
```
**Get the current digest:**
```bash
docker pull node:20-slim
docker inspect node:20-slim --format='{{index .RepoDigests 0}}'
```
**Automate digest pinning** with Renovate or Dependabot:
```json
// .renovaterc.json
{
"extends": ["config:base"],
"dockerfile": { "enabled": true },
"pinDigests": true
}
```
### 1.5 Never Bake Secrets into Images
```dockerfile
# ❌ NEVER — secret in ENV or RUN; visible in `docker history` and layer cache
ENV AWS_SECRET_ACCESS_KEY=supersecret
RUN curl -H "Authorization: Bearer $TOKEN" https://api.example.com > config.json
ARG API_KEY # Also unsafe — visible in build args history
# ✅ CORRECT — BuildKit secret mount (never persisted in any layer)
# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=api_token \
curl -H "Authorization: Bearer $(cat /run/secrets/api_token)" \
https://api.example.com/config > config.json
```
Build with: `docker build --secret id=api_token,src=./token.txt .`
**Check your image for leaked secrets:**
```bash
docker history --no-trunc myapp:latest | grep -iE "secret|key|password|token"
trivy image --scanners secret myapp:latest
```
### 1.6 Read-Only Filesystem & No New Privileges
```dockerfile
# In the Dockerfile — use exec form (no shell interpretation)
ENTRYPOINT ["node", "server.js"] # ✅ exec form
# ENTRYPOINT /bin/sh -c "node..." # ❌ shell form — spawns extra process
# Define a HEALTHCHECK
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD ["node", "-e", "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"]
```
Enforce read-only at runtime (see Layer 3).
### 1.7 Minimal .dockerignore
```dockerignore
# Always exclude these from build context
.git
.github
.env
.env.*
*.pem
*.key
node_modules
__pycache__
.pytest_cache
coverage/
dist/
*.log
.DS_Store
Dockerfile*
docker-compose*
README.md
docs/
tests/
```
### 1.8 Full Hardened Dockerfile Example
```dockerfile
# syntax=docker/dockerfile:1
# ── Build stage ───────────────────────────────────────────
FROM node:20-slim AS builder
WORKDIR /build
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci
COPY . .
RUN npm run build && npm prune --production
# ── Runtime stage ─────────────────────────────────────────
FROM gcr.io/distroless/nodejs20-debian12@sha256:<pin-digest-here>
LABEL org.opencontainers.image.source="https://github.com/org/repo"
LABEL org.opencontainers.image.revision="${BUILD_SHA}"
LABEL org.opencontainers.image.licenses="MIT"
WORKDIR /app
COPY --from=builder --chown=nonroot:nonroot /build/dist ./dist
COPY --from=builder --chown=nonroot:nonroot /build/node_modules ./node_modules
USER nonroot:nonroot
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD ["node", "-e", "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode===200?0:1))"]
CMD ["dist/server.js"]
```
---
## Layer 2: Image Scanning
### 2.1 Trivy (Recommended — Fast, Comprehensive)
```bash
# Install
brew install trivy # macOS
apt install trivy # Debian/Ubuntu
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \
-o "$tmpdir/trivy-install.sh"
sed -n '1,160p' "$tmpdir/trivy-install.sh"
sh "$tmpdir/trivy-install.sh"
# Scan an image for CVEs
trivy image myapp:latest
# Fail CI on HIGH/CRITICAL severity
trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:latest
# Scan Dockerfile for misconfigurations
trivy config ./Dockerfile
# Scan entire repo (vulnerabilities + secrets + misconfigs)
trivy fs --scanners vuln,secret,misconfig .
# Generate SBOM (CycloneDX or SPDX)
trivy image --format cyclonedx --output sbom.json myapp:latest
trivy image --format spdx-json --output sbom.spdx.json myapp:latest
# Ignore specific CVEs (add justification comments)
trivy image --ignorefile .trivyignore myapp:latest
```
**.trivyignore example:**
```
# CVE-2023-1234 — only exploitable via X feature, not used in this app
CVE-2023-1234
# CVE-2023-5678 — fix not yet available; tracked in issue #42
CVE-2023-5678
```
### 2.2 Grype (Anchore Alternative)
```bash
# Install
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh \
-o "$tmpdir/grype-install.sh"
sed -n '1,160p' "$tmpdir/grype-install.sh"
sh "$tmpdir/grype-install.sh"
# Scan image
grype myapp:latest
# Fail on critical
grype myapp:latest --fail-on critical
# Output SARIF for GitHub Security tab
grype myapp:latest -o sarif > results.sarif
# Pair with Syft for SBOM generation
syft myapp:latest -o cyclonedx-json > sbom.json
grype sbom:sbom.json # Scan the SBOM directly
```
### 2.3 Hadolint — Dockerfile Linting
```bash
# Run directly
docker run --rm -i hadolint/hadolint < Dockerfile
# With config file
hadolint --config .hadolint.yaml --failure-threshold warning Dockerfile
```
**.hadolint.yaml:**
```yaml
failure-threshold: warning
ignore:
- DL3008 # Pin versions in apt-get (allow floating for base layer)
trustedRegistries:
- gcr.io
- ghcr.io
- public.ecr.aws
```
### 2.4 Secret Scanning in Images
```bash
# Trivy covers secrets too
trivy image --scanners secret myapp:latest
# Dedicated: TruffleHog
trufflehog docker --image myapp:latest
# git-secrets to prevent committing secrets
git secrets --scan
```
### 2.5 CI Integration (GitHub Actions — SHA-Pinned)
```yaml
permissions:
contents: read
security-events: write # Required for uploading SARIF
jobs:
security-scan:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Build imag
… (truncated)Want a live grade + an embeddable README badge? Run your skill through the free scanner.
Graded independently by Skillproof — nothing to sell the author. Quality is mechanical + corpus-grounded; safety flags are heuristic (builtin+triage), not a malicious verdict.