Home / Courses / Security for Beginners / II — Hands-On Practice
II — Hands-On Practice
Article 7 of 8

Hands-On: Dependency & Container Security Scanning

Supply chain security — scanning dependencies with pip-audit, container hardening with a non-root user, and other good habits for a safer Docker image.

July 10, 2026

So far we've secured the code WE wrote ourselves. But task-tracker-api also depends on dozens of third-party packages (FastAPI, SQLModel, bcrypt, etc.) and a base Docker image we didn't write ourselves. Vulnerabilities there are just as dangerous — often more so, because we're not always aware we're relying on them. This is called supply chain security.

Why a Dependency Can Become a Gap

Every pip install pulls someone else's code into our application, with the same level of trust as code we wrote ourselves. If one of our dependencies (or a dependency of a dependency, called a transitive dependency) has a publicly known vulnerability (recorded in a CVE database), our application automatically becomes vulnerable too — even though we never wrote the problematic line of code ourselves.

Scanning Dependencies With pip-audit

pip-audit is an official tool from the Python Packaging Authority that checks requirements.txt against a database of known vulnerabilities:

pip install pip-audit
pip-audit -r requirements.txt
No known vulnerabilities found

Clean at this point in time — but this is NOT a one-and-done check. New vulnerabilities keep getting discovered in existing packages (sometimes in packages considered safe for years). The right habit: run pip-audit regularly — ideally automatically in every CI pipeline (exactly the concept covered in CI/CD for Beginners, just add it as one new step in the test job).

If pip-audit finds a vulnerability, the fix is usually as simple as updating to the version that patches it:

pip-audit -r requirements.txt --fix

But always re-run the test suite after updating — a new version sometimes brings an API change that breaks existing code (a breaking change).

Container Hardening — Never Run as Root

By default, a Docker container runs as the root user inside it — if someone manages to exploit a flaw in our application to execute arbitrary code, root inside that container has far more leeway (writing to the entire container filesystem, and in some misconfiguration scenarios, even "escaping" the container).

Our Dockerfile explicitly creates and switches to a regular user:

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app ./app

# Never run as root
RUN useradd --create-home --shell /usr/sbin/nologin appuser
USER appuser

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Prove it directly:

docker build -t security-demo-app .
docker run --rm security-demo-app whoami
appuser

Not root. This is the least privilege principle — any process should run with the MINIMUM access it needs to do its job, not the most leeway "just in case, to avoid permission errors." Our application never needs to install new software or modify system files at runtime — so there's no reason it needs to run as root.

Other Good Habits for Docker Images

A few additional practices already — or that should be — applied:

  • A minimal base image (python:3.11-slim, not plain python:3.11) — the less software installed in an image, the smaller its attack surface. Every extra tool the application doesn't use is an unnecessary potential gap.
  • Don't copy unnecessary files — our Dockerfile only does COPY app ./app, not the entire project directory. Files like .env, tests/, or .git/ never make it into the production image.
  • Pin the base image versionpython:3.11-slim should ideally be pinned to a more specific tag (e.g. python:3.11.9-slim) in a production environment, so builds are always deterministic and don't silently change the moment a new release ships that might introduce a problem.
  • Summary

  • Vulnerabilities can come from third-party dependencies, not just code we write ourselves — this is called supply chain security.
  • pip-audit checks dependencies against a database of known vulnerabilities — run it regularly, ideally automated in CI.
  • A container should never run as root — create a dedicated user with minimal privileges.
  • A minimal base image and copying only what's needed shrink a Docker image's attack surface.
  • Topics

    SecurityDependenciesDocker