GitHub Action¶
Pack in CI with no shell glue. The composite action downloads the signed release binary for the
runner, verifies it against the release checksums, and runs pack:
- uses: schubydoo/scratchsmith@v<ver> # pin to a release tag
with:
binary: ./dist/app # your prebuilt dynamic glibc binary
sbom: true # needs syft on the runner
strip: true
smoke: true # fail the job if the packed image can't start
To publish the built image, log in first and set push:
- uses: docker/login-action@v3
with: { registry: ghcr.io, username: ${{ github.actor }}, password: ${{ secrets.GITHUB_TOKEN }} }
- uses: schubydoo/scratchsmith@v<ver> # pin to a release tag
with:
binary: ./dist/app
push: ghcr.io/${{ github.repository }}:latest
Nine inputs take one value per line: cmd, env, label, healthcheck, add-file,
locale, include, deny, and require. Set image metadata, gate on vulnerabilities, or cap
the size:
- uses: schubydoo/scratchsmith@v<ver>
with:
binary: ./dist/app
entrypoint: /app
env: |
LANG=C.UTF-8
workdir: /data
label: |
org.opencontainers.image.source=https://github.com/you/app
role=api
healthcheck: |
/app
--health
scan: true
scan-fail-on: high # needs grype on the runner
max-size: 25MB
Pin @v<ver> to a specific release tag (or a commit SHA). That is the same supply-chain hygiene the
tool itself practices. version: overrides which scratchsmith release the action runs (defaults to
the pinned tag, else latest).
Inputs¶
Most inputs map to the pack flag of the same name. See Configuration for what
each does, and Usage for the equivalent command-line recipes. A few inputs are
action-specific. version picks which release to download. output maps to --no-build --output.
args is a verbatim escape hatch for any flag without a dedicated input, for example --sign or
--oci-archive. Note that push publishes with docker tag/docker push, not pack's
daemonless, cosign-signable --push.
| Input | Default | Description |
|---|---|---|
binary |
(required) | Path to the dynamically linked glibc ELF binary to pack. |
version |
(the pinned tag, else latest) |
Which scratchsmith release to download: a tag like v1.0.0, or latest. |
output |
Stage the rootfs into this directory instead of building an image (--no-build). |
|
entrypoint |
(the binary's path) | Image ENTRYPOINT. |
cmd |
Default arguments appended to the entrypoint, one per line. | |
env |
Image environment entries, one KEY=VALUE per line. |
|
workdir |
Image WORKDIR. |
|
user |
65532 |
Image user UID[:GID]. 0 warns. |
label |
OCI image labels, one KEY=VALUE per line. |
|
healthcheck |
HEALTHCHECK in exec form, one token per line. It must name an executable in the image. |
|
strip |
false |
Strip symbols from the binary and libraries. |
upx |
false |
Compress the binary with UPX (needs upx on the runner). |
smoke |
false |
After the build, run the image once. If the binary cannot start, the job fails. |
sbom |
false |
Generate an SBOM of the packed rootfs (needs syft on the runner). |
sbom-file |
sbom.json |
SBOM output path. Requires sbom, and is ignored on its own. |
sbom-format |
cyclonedx-json |
SBOM format: cyclonedx-json or spdx-json. |
scan |
false |
Vulnerability-scan the rootfs with grype (needs grype on the runner). When sbom is set, it reuses that SBOM. |
scan-fail-on |
Fail on a grype finding at or above this severity (negligible…critical). Implies scan. |
|
ca-certs |
false |
Add the TLS CA bundle to the image. |
tz |
false |
Add the resolved local timezone to the image. |
init |
false |
Add a minimal init (tini) as pid 1 wrapping the entrypoint. |
add-file |
Copy host files into the image, one SRC:DST per line. A bare absolute SRC keeps its own path. Regular files only, and a missing source, a directory, or a DST already in the image fails the pack. A symlinks mode that stages a link changes two of those rules for a symlink source. A preserved link is staged as a link, so a link to a directory no longer fails. Under skip-unsafe, an entry the pack cannot honor stages nothing and warns. A taken DST still fails under every mode. The file lands owned by uid 0 at mode 0644, or 0755 for an executable source, so do not add a secret this way. |
|
locale |
Compiled glibc locales to stage at /usr/lib/locale, one name per line, for example en_US.UTF-8. A name, never a path. Needs a prebuilt directory on the runner, or localedef plus the glibc sources at /usr/share/i18n. Staging a locale does not select it, so also set LANG through env. The host locale-archive is never copied. |
|
symlinks |
copy-all |
What a symlink you name becomes in the image: copy-all, preserve, copy-unsafe, or skip-unsafe. Covers the packed binary's own path and each add-file source, never the resolved libraries. |
include |
Extra libraries to force-stage, for example dlopen'd plugins, one soname/path per line. |
|
nss |
files,dns |
NSS modules to stage for glibc lookups, comma-separated (files,dns, or none). Fewer modules trim CVE surface. |
max-size |
If the fully-staged image exceeds this size, the job fails. Write the size as 25MB, 512KiB, or a byte count. |
|
deny |
If a listed library ships, fail the pack (soname or staged file name), one per line. | |
require |
If a listed library does not ship, fail the pack (same scope as deny), one per line. |
|
push |
Tag the built image as this registry ref and docker push it. Needs a prior registry login. Not compatible with output. |
|
args |
Extra raw pack flags appended verbatim: the escape hatch for anything above. |
Outputs¶
| Output | Description |
|---|---|
image |
The loaded image tag. When output staged a rootfs instead, this is empty. |
rootfs |
The staged rootfs directory. When an image was built instead, this is empty. |
report |
The full pack report as JSON. |