Pin It or Bin It
3 mins read

Pin It or Bin It


How to Stop Shipping Garbage Before It Ships You

If your build still pulls ubuntu:latest, you’re not “cloud-native.” You’re running a randomizer in production.

Why Pinning Matters

Every tag that isn’t a digest is a liability.
:latest is not convenience; it’s negligence.

What you write What happens next
FROM node:latest You rebuild on Tuesday and ship a new kernel vulnerability.
pip install flask You pull in a supply-chain bomb before lunch.
terraform init You silently upgrade the AWS provider, then wonder why IAM exploded.

If you don’t pin it, you can’t attest it.
If you can’t attest it, you can’t trust it.

The Tools of the Trade

OPA (Open Policy Agent)

A fast, embeddable policy engine.
It doesn’t care what file you feed it — only that you speak JSON.

Conftest

A CLI built on top of OPA.
It handles the boring bits:

  • parses Dockerfiles, YAML, Terraform, JSON
  • feeds them into OPA
  • exits 1 on fail, 0 on pass
    That’s all you need for CI or pre-commit hooks.

Install it:

# macOS / Linux
brew install conftest@0.56.0
# or
curl -L https://github.com/open-policy-agent/conftest/releases/latest/download/conftest_$(uname -s)_$(uname -m).tar.gz \
  | tar -xz -C /usr/local/bin

Run it:

conftest test Dockerfile --parser dockerfile

It will parse your Dockerfile into JSON, run your Rego rules, and tell you exactly why your laziness failed policy.

Crane

Part of Google’s go-containerregistry toolkit.
It’s the BOFH’s Swiss army knife for container registries.

Use it to fetch digests, manifests, and metadata — fast, no daemon, no Docker socket.

Install it:

brew install go-containerregistry
# or
go install github.com/google/go-containerregistry/cmd/crane@0.20.02

Pin any image:

img=python:3.12
echo "$img@$(crane digest "$img")"

Result:

python:3.12@sha256:deadbeefbadc0ffee123456789...

Now you know exactly which bits are running in prod.
No excuses, no surprises.

🪓 Policy as Code: The OPA Way

Create policies/dockerfile/pinning.rego:

package policies.dockerfile.pinning

deny[msg] {
  some s
  f := s.Commands[_]
  f.Name == "from"
  not contains(f.Value, "@sha256:")
  msg := sprintf("FROM %q missing digest", [f.Value])
}

deny[msg] {
  f := input.Stages[_].Commands[_]
  f.Name == "user"
  trim(f.Value) == "root"
  msg := "Dockerfile must set non-root USER"
}

Test it manually:

conftest test Dockerfile --parser dockerfile --output table

Expected output:

FAIL - Dockerfile - FROM "ubuntu:latest" missing digest

🧷 Make It Automatic: Pre-commit Hook

.git/hooks/pre-commit:

#!/usr/bin/env sh
set -eu
conftest pull ghcr.io/your-org/precommit:1 >/dev/null || true
files=$(git diff --cached --name-only | grep -Ei '(Dockerfile|\.ya?ml)$' || true)
[ -z "$files" ] && exit 0
conftest test --parser=dockerfile --output table $files

Try to commit a floating Dockerfile and watch it fail:

FAIL - Dockerfile - FROM "nginx:latest" missing digest
✖ OPA pre-commit checks failed.

Success: laziness stopped at the gate.

The Philosophy

  • Small tools. crane for digests. conftest for checks. jq and yq for glue.
  • Zero GUIs. If it doesn’t fit in a pipeline, it doesn’t belong.
  • Fast failure. The only “UX” a BOFH cares about is exit 1.
  • Determinism > decoration. Pretty dashboards are for people who don’t read logs.

TL;DR

  1. Pin everythingimage@sha256:digest.
  2. Write Rego policies once; run everywhere.
  3. Use Conftest in pre-commit and CI.
  4. Use Crane to fetch digests and enforce reproducibility.
  5. Automate the yelling so you can focus on real problems.



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *