Skip to content

Running with full hardening

The compose-lint Docker image is safe-by-default (distroless, nonroot, read-only attack surface), so the simple docker run --rm -v "$(pwd):/src" composelint/compose-lint form is fine for most use.

If you want to dogfood compose-lint's own rules against the container that runs it, the fully-hardened invocation is:

docker run --rm \
  --read-only \
  --cap-drop ALL \
  --security-opt no-new-privileges:true \
  --network none \
  --user 65532:65532 \
  --memory 256m \
  --cpus 0.5 \
  --pids-limit 256 \
  -v "$(pwd):/src:ro" \
  composelint/compose-lint:0.17.0
Flag Rule satisfied
--security-opt no-new-privileges:true CL-0003
--cap-drop ALL CL-0006
--read-only CL-0007
--memory 256m + --cpus 0.5 CL-0026
--user 65532:65532 CL-0018 (matches the image's existing default)
--pids-limit 256 — defence in depth; no rule flags its absence. CL-0012 used to, and was removed: on a mainstream systemd host the limit is bounded by DefaultTasksMax whether or not you set it

--network none and :ro on the bind mount are extra hardening — compose-lint never reaches the network and only reads its inputs.

For full supply-chain reproducibility (and to satisfy CL-0004 / CL-0019), replace the version tag with a digest pin: composelint/compose-lint@sha256:<digest>. Get the current digest from Docker Hub or with docker buildx imagetools inspect composelint/compose-lint:0.17.0 --format '{{json .Manifest}}' | jq -r '.digest'.

A Compose-form equivalent that lints clean across every rule lives in tests/compose_files/safe_self_hosted.yml.