Vulnerable Dockerfile Anti-Patterns
Each section shows the bad pattern, why it's dangerous, and the fix. Useful as a checklist when reviewing Dockerfiles or generating training examples for detection rules.
1. Running as root
# BAD
FROM python:3.11
COPY . /app
CMD ["python", "/app/main.py"]Default USER root — any RCE is instantly root in the container, and any
capability not explicitly dropped is live.
# GOOD
FROM python:3.11-slim
RUN groupadd -r app && useradd -r -g app app
COPY --chown=app:app . /app
USER app
CMD ["python", "/app/main.py"]2. latest tag / unpinned base
# BAD
FROM node:latestNon-reproducible; supply-chain attackers can swap the tag.
# GOOD — pin by digest
FROM node:20.11.1-bookworm-slim@sha256:abc123...3. Secrets baked in
# BAD
ARG AWS_SECRET_ACCESS_KEY
ENV AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
COPY .env /app/.envPersists in layer history (docker history) and SBOM; rotatable only by
rebuilding and redeploying.
# GOOD — BuildKit secrets
# syntax=docker/dockerfile:1.7
RUN --mount=type=secret,id=aws \
aws s3 cp s3://bucket/file /tmp/4. ADD with remote URL
# BAD
ADD https://example.com/installer.sh /tmp/install.sh
RUN sh /tmp/install.shUnverified download, no checksum, TOCTOU.
# GOOD
COPY installer.sh /tmp/install.sh
RUN echo "<sha256> /tmp/install.sh" | sha256sum -c - \
&& sh /tmp/install.sh5. Package cache left behind
# BAD
RUN apt-get update && apt-get install -y curl gitIncreases size and exposes outdated metadata.
# GOOD
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl=7.88.* git=1:2.39.* \
&& rm -rf /var/lib/apt/lists/*6. chmod 777 / world-writable files
# BAD
RUN chmod -R 777 /appAny compromised process can rewrite application code.
# GOOD
RUN chmod -R o-w /app7. Curl-pipe-shell bootstrap
# BAD
RUN curl -fsSL https://get.example.com | bashNo signature check; mirror compromise = RCE at build time.
# GOOD
COPY bootstrap.sh .
RUN sha256sum -c bootstrap.sha256 && ./bootstrap.sh8. SSH server installed
# BAD
RUN apt-get install -y openssh-server
EXPOSE 22Containers should be immutable — use kubectl exec, not SSH.
9. Missing HEALTHCHECK
# BAD
# (no HEALTHCHECK)# GOOD
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl -fsS http://localhost:8080/healthz || exit 110. Shell-form ENTRYPOINT
# BAD — signals (SIGTERM) don't reach the process
ENTRYPOINT python /app/main.py# GOOD
ENTRYPOINT ["python", "/app/main.py"]Hadolint Quick Reference
| Rule | Meaning |
|---|---|
| DL3002 | Don't switch to root USER |
| DL3003 | Use WORKDIR instead of cd |
| DL3007 | Don't use latest tag |
| DL3008 | Pin apt-get package versions |
| DL3009 | Delete apt-get lists after installing |
| DL3020 | Use COPY not ADD for local files |
| DL3025 | Use JSON form for CMD/ENTRYPOINT |
| DL4006 | Set SHELL ["/bin/bash", "-o", "pipefail", "-c"] |