Dockerfile Validator
Overview
This skill validates Dockerfiles using a single self-contained script (dockerfile-validate.sh) that handles everything: tool installation, validation, and cleanup.
Key Features:
- ✅ Single script execution - no dependencies on other scripts
- ✅ Auto-installs hadolint and Checkov in Python venvs if not found
- ✅ Runs all 4 validation stages (syntax, security, best practices, optimization)
- ✅ Auto-cleanup on exit using bash trap (success or failure)
- ✅ Zero configuration required
Quick Start
Single command to validate any Dockerfile:
bash scripts/dockerfile-validate.sh Dockerfile
The script automatically checks for hadolint and Checkov, installs them temporarily in Python venvs if needed, runs all 4 validation stages, then cleans up on exit.
Validation Workflow
The dockerfile-validate.sh script runs a comprehensive 4-stage validation:
- Auto-Install (if needed) — Check for hadolint and Checkov; install in Python venvs if absent; set flag to trigger cleanup on exit via bash trap
- [1/4] Syntax Validation (hadolint) — Dockerfile syntax checking, instruction validation, shell script validation via ShellCheck
- [2/4] Security Scan (Checkov) — Security policy validation, hardcoded secret detection, port exposure checks, USER directive validation
- [3/4] Best Practices Validation (custom) — Base image tag validation, non-root USER enforcement, HEALTHCHECK presence, layer efficiency, package cache cleanup, COPY ordering
- [4/4] Optimization Analysis (custom) — Base image size analysis, multi-stage build opportunities, layer count, .dockerignore check, build structure recommendations
- Auto-Cleanup (always runs) — Remove temp venvs on exit (success, failure, Ctrl+C, or error)
Core Capabilities
1. Syntax Validation with hadolint
Workflow:
# Run hadolint on Dockerfile
hadolint Dockerfile
# Run with JSON output for parsing
hadolint --format json Dockerfile
# Run with specific rules ignored
hadolint --ignore DL3006 --ignore DL3008 Dockerfile
# Using Docker if not installed
docker run --rm -i hadolint/hadolint < Dockerfile
For the full list of DL-prefixed (hadolint) and SC-prefixed (ShellCheck) rules, see references/docker_best_practices.md.
Rule Severity Levels: error → warning → info → style
Best Practices:
- Run hadolint before every docker build
- Integrate into CI/CD pipelines
- Configure
.hadolint.yamlfor project-specific rules - Address errors before warnings
2. Security Scanning with Checkov
Installation (permanent):
pip3 install checkov # direct
brew install checkov # macOS
Workflow:
# Scan a Dockerfile
checkov -f Dockerfile --framework dockerfile
# Scan a directory (finds all Dockerfiles)
checkov -d . --framework dockerfile
# Compact output (failures only)
checkov -f Dockerfile --framework dockerfile --compact
# Skip specific checks
checkov -f Dockerfile --framework dockerfile --skip-check CKV_DOCKER_2
For the full list of CKV_DOCKER_* security checks, see references/security_checklist.md.
Suppressing False Positives:
# checkov:skip=CKV_DOCKER_2:Health check not applicable for this init container
FROM alpine:3.21
Best Practices:
- Run Checkov after hadolint (syntax first, then security)
- Address high-severity findings first
- Document all suppressions with clear justification
- Integrate into CI/CD pipelines
3. Best Practices Validation
Custom Validation Checks:
# Check for :latest tag usage
grep -E "^FROM.*:latest" Dockerfile
# Count FROM statements (single FROM = multi-stage opportunity)
grep -c "^FROM" Dockerfile
# Ensure USER is set before CMD/ENTRYPOINT
grep "^USER" Dockerfile
# Verify HEALTHCHECK is defined for services
grep "^HEALTHCHECK" Dockerfile
# Count RUN commands (>5 suggests combination opportunity)
grep -c "^RUN" Dockerfile
# Verify cache cleanup in same RUN layer
grep "rm -rf /var/lib/apt/lists" Dockerfile
grep "--no-cache" Dockerfile # for apk
Non-Obvious Checks (project-specific):
| Category | Non-Obvious Convention |
|---|---|
| Base Images | Prefer digest pinning (FROM alpine@sha256:…) over version tags for reproducible builds |
| Layer Ordering | COPY dependency manifests before source code so edits don't bust the install cache |
| Cache Cleanup | Must happen in the same RUN layer as the install; a separate RUN rm -rf … creates a new layer that doesn't reduce size |
| Multi-Stage | Named stages (AS build) allow selective --target build for CI debugging without exposing secrets in final image |
| Secrets | BuildKit --mount=type=secret avoids secrets ever appearing in any layer, including intermediate ones |
4. Optimization Analysis
Optimization Categories:
Image Size Reduction:
# Bad: Full distro
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl
# Good: Minimal distro
FROM alpine:3.21
RUN apk add --no-cache curl
# Better: Multi-stage with distroless
FROM golang:1.21 AS build
WORKDIR /app
COPY . .
RUN go build -o myapp
FROM gcr.io/distroless/base-debian11
COPY --from=build /app/myapp /
ENTRYPOINT ["/myapp"]
Layer Optimization:
# Bad: Separate RUN commands (creates many layers)
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
# Good: Combined RUN (single layer)
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
Build Cache Efficiency:
# Bad: Copy all, then install dependencies
COPY . /app
RUN pip install -r requirements.txt
# Good: Copy dependency file first
COPY requirements.txt /app/
RUN pip install -r requirements.txt
COPY . /app
5. .dockerignore Validation
# Check if .dockerignore exists
if [ ! -f .dockerignore ]; then
echo "WARNING: .dockerignore file not found"
fi
Common patterns to include:
.git
.gitignore
.env
*.log
node_modules
Dockerfile*
docker-compose*.yml
Tool Prerequisites
The validation script automatically installs tools if not found. No manual installation required.
For permanent installations:
# hadolint
brew install hadolint # macOS
wget -O ~/.local/bin/hadolint https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64 && chmod +x ~/.local/bin/hadolint # Linux
# Checkov
pip3 install checkov
Minimum Versions: hadolint >= 2.12.0 | Checkov: latest | Python >= 3.8
Testing Auto-Install:
# Force temporary installation for testing
FORCE_TEMP_INSTALL=true bash scripts/dockerfile-validate.sh Dockerfile
Handling Missing Tools
When tools are not installed, the script auto-installs them temporarily. If auto-install fails:
- Complete available validations — continue with custom best practices checks and note skipped stages
- Provide installation guidance:
- hadolint:
brew install hadolint(macOS) or wget from GitHub releases (Linux) - Checkov:
pip3 install checkov
- hadolint:
- Offer to rerun after installation
Tool Priority:
- Required (always run): Custom best practices validation, file existence checks
- Recommended: hadolint (syntax/linting), Checkov (security scanning)
- Optional: docker (test builds), trivy (vulnerability scanning)
Error Troubleshooting
| Error | Solution |
|---|---|
FROM instruction must be first non-comment |
Move ARG VERSION=18 before FROM node:${VERSION} |
| Unknown instruction (typo) | Check spelling: common typos are RUNS, COPIES, FRUM |
| Chained RUN command fails | Use apt-get install -y package || exit 1 or set -e |
COPY failed: file not found |
Check path is relative to build context; verify not excluded by .dockerignore |
| Hardcoded secrets detected | Use BuildKit secrets: docker build --secret id=api_key,src=api_key.txt instead of ENV API_KEY=secret123 |
| Slow builds | Optimize layer caching (COPY package files first), use .dockerignore, enable BuildKit (export DOCKER_BUILDKIT=1), use multi-stage builds |
Mandatory Workflow
Follow these steps in order for every validation request:
1. Pre-Validation
- Read the Dockerfile first — Use the Read tool to examine the Dockerfile before running validation.
2. Validation
- Run the validation script — Execute
bash scripts/dockerfile-validate.sh <Dockerfile>to run all 4 stages.
3. Post-Validation
Summarize findings by severity:
- Critical (security vulnerabilities, hardcoded secrets)
- High (missing USER, HEALTHCHECK, :latest tags)
- Medium (layer optimization, version pinning)
- Low (style, informational)
Propose specific fixes — Use the Read tool to load appropriate reference files before proposing fixes:
Issue Type Reference File Security issues (secrets, USER, ports) references/security_checklist.mdSize/performance optimization references/optimization_guide.mdGeneral best practices references/docker_best_practices.mdOffer to apply fixes — Ask the user if they want fixes applied, then apply if approved.
Example interaction:
User: "Validate my Dockerfile"
1. Read the Dockerfile using Read tool
2. Run: bash scripts/dockerfile-validate.sh Dockerfile
3. Review output from all 4 stages
4. Summarize findings by severity (critical → low)
5. Use Read tool to load relevant reference files:
- Read references/security_checklist.md (if security issues found)
- Read references/optimization_guide.md (if optimization issues found)
- Read references/docker_best_practices.md (if best practice issues found)
6. Propose specific fixes with code examples from reference content
7. Ask: "Would you like me to apply these fixes?"
8. Apply fixes if user approves
For multi-Dockerfile projects: find all Dockerfile* files, validate each sequentially, aggregate results, and provide a unified report with project-wide improvement suggestions.
Integration with Other Skills
- dockerfile-generator — Generate optimized Dockerfiles
- k8s-yaml-validator — Validate Kubernetes deployments that reference Docker images
- helm-validator — Validate Helm charts with container configurations
Notes
- Always validate before building images
- Address security issues before optimizations
- Test builds after applying fixes
- Version pin base images for reproducibility
- Use multi-stage builds for compiled languages
- Keep production images minimal (distroless, Alpine)
- Never commit Dockerfiles with hardcoded secrets
- Document inline suppressions with clear justification
- Regularly update base images for security patches
- Integrate validation into CI/CD pipelines
Anti-Patterns
NEVER skip Hadolint because the image builds successfully
- WHY: A Dockerfile that builds can still violate security, layer caching, and size best practices; hadolint catches these before they reach production.
- BAD: Skip linting if
docker buildexits 0. - GOOD: Run
hadolint Dockerfilein CI as a required check; treat W rules as warnings and DL rules as errors.
NEVER ignore DL3008 (unpinned apt packages)
- WHY: Unpinned packages produce non-reproducible images; the same Dockerfile built a month apart may install different package versions with different security postures.
- BAD:
RUN apt-get install -y curl - GOOD:
RUN apt-get install -y curl=7.88.1-*or use a digest-pinned base image with pre-installed versions.
NEVER validate a Dockerfile without also scanning the built image
- WHY: Static analysis misses runtime vulnerabilities introduced by base images and installed packages; image scanning (Trivy, Grype) is complementary.
- BAD: Pass hadolint and ship the image without scanning it.
- GOOD: Run
trivy image myapp:latestafter build in the CI pipeline.
NEVER treat --no-cache as a security measure
- WHY:
--no-cacheprevents layer caching during the current build but does not remove vulnerabilities in the base image or installed packages; image scanning is still required. - BAD: Rely on
--no-cacheto ensure a "fresh" secure image. - GOOD: Use
--no-cachefor reproducibility in CI; pair with image scanning for security assurance.
References
Internal:
scripts/
dockerfile-validate.sh
- Single self-contained validation script
- Auto-installs hadolint and Checkov if needed
- Runs all 4 validation stages (syntax, security, best practices, optimization)
- Auto-cleanup on exit
- Usage:
bash scripts/dockerfile-validate.sh [Dockerfile]
assets/
| File | Purpose |
|---|---|
good-example.Dockerfile |
Best practices and optimal structure |
bad-example.Dockerfile |
Common mistakes and anti-patterns |
security-issues.Dockerfile |
Intentional security vulnerabilities for testing |
python-optimized.Dockerfile |
Python-specific optimizations and multi-stage build |
golang-distroless.Dockerfile |
Minimal Go application using distroless base image |
.dockerignore.example |
Example .dockerignore for build context optimization |
references/
| File | Contents |
|---|---|
docker_best_practices.md |
Official Docker best practices; full hadolint DL/SC rule listings |
optimization_guide.md |
Layer optimization and image size reduction techniques |
security_checklist.md |
Container security best practices; full CKV_DOCKER_* check listings |
External:
Official Docker Documentation:
Security Guidelines:
Best Practices Resources: