Skip to main content

Rebuild & harden an image

A policy with no transform is copied byte-for-byte and stamped (see Getting started). Add a transform and houba switches to the rebuild path: it rebuilds the image through BuildKit applying declarative hardening primitives, then stamps the result with base.digest = the source digest plus io.houba.transform.* lineage. The policy stays portable — it only names the CA(s) and mirror; the org-specific data lives in configuration. For the concepts behind this, see Transforms & signed attestations.

1. Declare the transform in the policy

The hardened/redis.yml example injects an internal CA and rewrites package sources to an internal mirror:

imports:
- name: v7
owners:
- group:default/data-platform # stamped as io.houba.owners
tags:
includeRegex: "^7\.2\."
transform:
- injectCA: { certs: [corp] } # names → HOUBA_TRANSFORM_CA_CERTS
- rewritePackageSources: { mirror: corp } # name → HOUBA_TRANSFORM_PACKAGE_MIRRORS
destinations:
- project: hardened
repository: redis

2. Supply the org-specific config

The policy only names corp; the actual cert path and mirror URLs are configuration, so the same policy is portable across orgs:

export HOUBA_TRANSFORM_CA_CERTS='{"corp": {"path": "/etc/houba/certs/corp-root.pem"}}'
export HOUBA_TRANSFORM_PACKAGE_MIRRORS='{"corp": {"apt": "https://mirror.corp/debian", "apk": "https://mirror.corp/alpine"}}'

3. Reconcile (rebuild)

houba reconcile docs/examples/hardened
# rebuilt docker.io/library/redis:7.2.x → hardened/redis:7.2.x
# base.digest=sha256:src… transform=injectCA,rewritePackageSources
note

buildctl must be on PATH (the runtime image bundles it). Without a transform, this same command would copy instead of rebuild.

4. Inspect the stamp

regctl image config registry.example.com/hardened/redis:7.2.0 \
| jq '.config.Labels | with_entries(select(.key | startswith("io.houba")))'
# io.houba.transform.steps = "injectCA,rewritePackageSources"
# io.houba.owners = "group:default/data-platform"

5. Sign the rebuild (verifiable provenance)

attested/redis.yml is the same rebuild with signing on. Signing is org configuration, not policy — the file stays portable:

export HOUBA_ATTEST_SIGNER=keyless # or: kms | key
export HOUBA_ATTEST_BUILDER_ID=https://houba.example/builders/main
# keyless: optional internal CA + transparency log (blank rekor => no log entry)
export HOUBA_ATTEST_FULCIO_URL=https://fulcio.corp

houba reconcile docs/examples/attested
# rebuilt … → attested/redis:7.2.x
# signed: https://houba.dev/predicate/transform/v1 → sha256:att…
# signed: https://slsa.dev/provenance/v1 (buildkit) → sha256:att…
note

cosign must be on PATH. Off by default: with no HOUBA_ATTEST_SIGNER, the rebuild is stamped but unsigned.

To verify the result and the attached SBOM, see Inspect an image's SBOM. For every signer knob, see the configuration reference.