CoursesAdvanced scripting for DevSecOpsProduction hardening & delivery

Packaging & distribution: pinning, pex/shiv & containers

Hash-pinned deps, single-file zipapps, and distroless tool containers.

Advanced35 min · lesson 13 of 15

A tool that only runs on the author’s laptop is a liability. Shipping it means pinning exactly what it depends on so it is reproducible, packaging it so it installs cleanly, and — for anything that runs in CI or on servers — putting it in a minimal container so its runtime is controlled. The goal is that the tool you tested is byte-for-byte the tool that runs, everywhere.

Pin dependencies by hash, not by hope

requirements.txt with loose version ranges means two installs a week apart can pull different code — a supply-chain risk and a reproducibility hole. Pin every direct and transitive dependency to an exact version AND a hash, so pip refuses anything that does not match. pip-tools (or uv, or Poetry’s lock) compiles a fully-pinned, hashed lockfile from your high-level requirements; commit the lock and install from it with --require-hashes.

compile and install a hash-pinned lockfile
# requirements.in (what you actually want)
# click>=8
# httpx>=0.27
# compile to a fully pinned, hashed lockfile
pip-compile --generate-hashes -o requirements.txt requirements.in
# requirements.txt now contains, for every transitive package:
# click==8.1.7 \
# --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28
# install with hash enforcement — a tampered or swapped package is rejected
pip install --require-hashes -r requirements.txt

Single-file executables with pex/shiv

Sometimes you want to hand someone one file that just runs, without a virtualenv dance. pex and shiv build a zipapp: your code plus its dependencies bundled into a single executable that runs on any compatible Python. It is not a static binary (it still needs a Python interpreter), but it removes "did you pip install everything" as a failure mode and makes distribution a copy.

build a self-contained zipapp
# shiv: bundle the package + deps into one executable file
shiv -c sec-rotate -o sec-rotate.pyz sec-tools
# runs anywhere with a compatible python, no venv setup
./sec-rotate.pyz --help
# pex is the other common choice (Pants/Twitter lineage)
pex sec-tools -c sec-rotate -o sec-rotate.pex --python-shebang='/usr/bin/env python3'

Minimal, non-root tool containers

For CI and server use, a container fixes the runtime exactly. Build it in stages — install and compile in a fat builder, copy only the artifact into a tiny final image — and run it as a non-root user on a distroless or slim base so the attack surface is a fraction of a full OS. The tool ships with its interpreter and its pinned deps and nothing else.

Dockerfile — multi-stage, distroless, non-root
FROM python:3.12-slim AS build
WORKDIR /app
COPY requirements.txt .
RUN pip install --require-hashes --no-cache-dir --target=/deps -r requirements.txt
COPY . .
# distroless: no shell, no package manager, tiny attack surface
FROM gcr.io/distroless/python3-debian12:nonroot
WORKDIR /app
COPY --from=build /deps /deps
COPY --from=build /app /app
ENV PYTHONPATH=/deps
USER nonroot
ENTRYPOINT ["python", "-m", "sec_tools.cli"]
From source to a reproducible artifact
1requirements.in
high-level intent
2lockfile + hashes
exact, verifiable deps
3build (venv / zipapp / image)
assembled once
4immutable artifact
the tested bytes are the shipped bytes
Every arrow removes a way for "works here, breaks there" to creep in; the artifact is pinned, hashed, and minimal.
curl | bash and unpinned installs are supply-chain holes
Installing your tool (or its deps) with an unpinned pip install, or bootstrapping with curl ... | bash, means you run whatever the source serves at that moment — including a compromised or typosquatted package. Pin and hash everything, install from the committed lockfile, and vendor or verify any bootstrap script. Reproducibility is a security property, not just a convenience.