Five changes separate a throwaway image from a liability.
Pin versions, multi-stage build, minimal base, non-root user, no baked-in secrets — the same before/after that best demonstrates real hardening knowledge.
Before
FROM ubuntu:latest RUN apt-get update && apt-get install -y python3 COPY . /app ENV AWS_SECRET_KEY=AKIA... CMD ["python3", "app.py"]
After
FROM python:3.12.3-slim AS builder # ...install deps in a build stage... FROM python:3.12.3-slim COPY --from=builder ... RUN useradd --create-home appuser USER appuser # secrets injected at runtime, never baked in
ChangeWhy it matters
Non-root USERIf exploited, the attacker runs unprivileged, not root.
Pinned versionThe same Dockerfile can't silently produce a different image later.
No baked-in secretsA secret in ENV is permanently embedded in the image's layer history.
The two changes with the biggest real-world impact: never run as root, and never bake secrets into image layers.