Lion Cipher Lab
Lint a Dockerfile for security with hadolint — catch the mistakes before the image exists
- Cloud Security
- Intermediate
- ~40 min
- Free · no account required
Why it matters
Almost every container vulnerability class starts as one line in a Dockerfile that nobody linted. `FROM ubuntu:latest` means your 'reproducible' build silently changes under you and you can't say which base you shipped. An `apt-get install curl` with no version pin means two builds a week apart contain different packages. A hardcoded `ARG DB_PASSWORD=...` bakes a secret into an image layer that anyone who pulls the image can extract. No `USER` line means your app runs as root inside the container, so a single app bug is a root bug. An `ADD https://…` pulls a remote artifact into the image…
What you'll learn
- explaining in plain words what hadolint is: a STATIC linter that reads a Dockerfile's TEXT and flags best-practice and security mistakes, before any image is built
- running hadolint two ways with zero install friction: the Docker image via stdin (docker run --rm -i hadolint/hadolint < Dockerfile) or the standalone binary from GitHub releases
- reading hadolint output: the file:line, the DL#### rule codes (hadolint's own) and the SC#### rule codes (from the ShellCheck engine it runs over RUN lines), and the severity
- defining the security-relevant rules cold — DL3007 (don't use latest), DL3008 (pin apt versions), DL3015 (--no-install-recommends), DL3009 (clean apt lists), DL3020 (COPY not ADD), DL4006 (pipefail), DL3002 (last USER must not be root), DL3025 (JSON-form CMD/ENTRYPOINT), SC2086 (quote variables) — and WHY each matters
- measuring: counting the findings on a deliberately-bad Dockerfile (the BEFORE), fixing each real issue, and re-running to zero or an all-triaged result (the AFTER)
- judgment: triaging findings — not every hadolint hit is security-critical (some are style/reproducibility) — and inline-ignoring a rule you consciously accept with `# hadolint ignore=DLxxxx`, plus documenting WHY the exception is safe
- stating the honest limit out loud: passing hadolint is NOT a secure image — hadolint reads the Dockerfile only, cannot see the base image's actual contents, and has no rule for a hardcoded secret it can't recognise — so you pair it with Trivy (image CVEs) and runtime hardening
- placing hadolint in the pipeline: authoring-time linting COMPLEMENTS Trivy (built-image CVE scanning), Checkov (IaC), distroless (smaller attack surface) and non-root/caps/seccomp (runtime hardening)
Steps in this lab
- From zero: what hadolint is, and how it differs from Trivy and Checkov
- Get hadolint running — the Docker image (easiest) or the binary
- Write the deliberately-bad Dockerfile
- Run hadolint, read the codes, and COUNT the findings (the BEFORE)
- Fix each real issue — and know WHY each matters for security
- Re-run hadolint and confirm the AFTER: the count is the measurement
- Judgment: triage findings and inline-ignore the ones you consciously accept
- The honest negative: a clean lint is NOT a secure image
- Where hadolint fits, in one honest opinion