Container images
Two images per release, linux/amd64 and linux/arm64. The first release with images is 0.35.0; the binaries on the release page go back further.
ghcr.io/ohartwig/pinup: pinup,git,gpgand its agent,ssh-keygen, a CA bundleghcr.io/ohartwig/pinup-toolchain: the same, pluscomposer,php,node,npm,yarnandgo
docker run --rm -v "$PWD:/workspace" ghcr.io/ohartwig/pinup:0 \
pinup whatif --repo . --config .pinup.yaml --report plan.jsonWhich one
The slim one. pinup needs a package manager only when an update has a lock file to regenerate — a composer.lock, a package-lock.json, a go.sum — or when the configuration runs a postUpgradeTask. Everything else is the binary and git.
The toolchain image is roughly five times the size, and every interpreter in it is a thing that can carry a CVE into your pipeline. Where an update needs one, pinup says so rather than guessing: the branch is held with the reason pluginRequired and stands that way in the plan.
The slim image asserts that it stays slim. The build fails if node or php turns up on the path. That is not decoration, it is the invariant the image exists for.
Tags
0.35.0— never moves. For reproducible pipelines and anything you audit.0.35— moves with each patch.0— moves with each minor. pinup is pre-1.0, so this line can carry behaviour changes.latest— moves with each release.
Only a digest (ghcr.io/ohartwig/pinup@sha256:…) cannot change under you. In a pipeline, pin tag and digest and let a bot move both — which is, after all, what pinup is for.
Where the binary comes from
The image does not compile pinup. The workflow downloads the binaries of that release, checks them against its SHA256SUMS and copies one in. They are built and signed once, upstream, and the same files are on the release page.
That split is deliberate. An earlier workflow rebuilt the binaries when the tag arrived: a second artefact under the release's name, with nobody's signature on it. Packaging a signed binary is not the same as building one, and only the first can be verified against what was published.
The recipes are in the repository under packaging/. The base is Chainguard's wolfi-base, pinned by digest. The toolchain image pins each language runtime to an exact Wolfi package version, so a bump is a reviewed diff rather than whatever the mirror served that morning.
Verifying what you pulled
Both images are signed keyless. The signature is made against the workflow's OIDC identity, so it verifies without a key from us.
cosign verify ghcr.io/ohartwig/pinup:0.35.0 \
--certificate-identity-regexp '^https://github.com/ohartwig/pinup/' \
--certificate-oidc-issuer token.actions.githubusercontent.com
Each image also carries an SBOM and its build provenance, attached as attestations in the index; docker buildx imagetools inspect fetches both. The scan runs before the signature: a fixable CRITICAL or HIGH fails the build, and an image that does not pass is never signed. A missing signature is therefore a statement, not an oversight.
Running it
The image runs as uid 1000 in /workspace. There is no entrypoint, so pinup is part of the command: docker run … pinup whatif …, not docker run … whatif …. That is deliberate. A CI runner starts the job script with sh in the container, and an entrypoint of pinup would answer that with “unknown command sh”.
docker run --rm \
-v "$PWD:/workspace" \
-e PINUP_GITLAB_URL -e PINUP_GITLAB_TOKEN \
ghcr.io/ohartwig/pinup:0 \
pinup run --project group/project --config 'local>group/runner' --report plan.json- Credentials come from the environment, one per platform. A run that only plans needs none.
- git ownership:
safe.directoryis set system-wide in the image, so a mounted checkout owned by another uid does not stop git. - Signing:
PINUP_SIGNING_FORMAT=openpgpneeds the key handed in. The image hasgpgand no key. - The cache (
PINUP_CACHE) belongs on a volume that survives the run: without it,minimumReleaseAgeis unreliable for registries that publish no timestamps.
If your runners cannot reach ghcr.io, mirror the image into your own registry and pin the digest you mirrored. A copied image stays verifiable as long as the digest travels with it.