Skip to content

Configuration

Instead of a long command line, put the defaults for pack in a scratchsmith.toml and load it with --config. Every key below maps to a pack flag, and a command-line flag overrides the file.

scratchsmith.toml key CLI flag What it does
binary (positional arg) The ELF binary to pack.
entrypoint --entrypoint Image ENTRYPOINT (defaults to the packed binary's path).
cmd --cmd Default arguments appended to the entrypoint (list, and --cmd is repeatable).
env --env Image environment entries, each KEY=VALUE (list).
workdir --workdir Image WORKDIR.
user --user Image user UID[:GID]. Defaults to a non-root UID. 0/root prints a warning.
label --label OCI image label KEY=VALUE (list, and --label is repeatable).
healthcheck --healthcheck Container HEALTHCHECK in exec form (list, and repeatable). It runs inside the scratch image, so it must name an executable present there, typically the packed binary.
strip --strip Strip symbols from the binary and libraries.
upx --upx Compress the packed binary with UPX (it self-decompresses at runtime).
smoke --smoke Run the built image once. If the binary cannot start, the pack fails.
sbom --sbom Write an SBOM of the packed rootfs (requires syft).
sbom-file --sbom-file SBOM output path (default: sbom.json).
sbom-format --sbom-format SBOM format: cyclonedx-json (default) or spdx-json.
scan --scan Vulnerability-scan the packed rootfs with grype. When --sbom is set, it reuses that SBOM. Otherwise it scans the rootfs.
scan-fail-on --scan-fail-on Fail the pack on a grype finding at or above this severity: negligible/low/medium/high/critical (implies --scan). negligible blocks everything, including findings grype cannot rank. Stricter levels ignore unrankable findings.
ca-certs --ca-certs Add the TLS CA bundle (/etc/ssl/certs/ca-certificates.crt).
tz --tz Add the resolved local timezone (/etc/localtime).
init --init Add a minimal init (tini) as pid 1 wrapping the entrypoint.
add-file --add-file Copy a host file into the image (list). Each entry is SRC:DST, or a bare absolute SRC to keep the host path. With the default symlinks = "copy-all" the entry takes regular files only. A missing source, a directory, or a DST already in the image then fails the pack. Another symlinks mode changes that for a source that is itself a symlink. In an image the file lands owned by uid 0 at mode 0644, or 0755 for an executable source. The layer writer canonicalizes modes, so do not add a secret this way.
locale --locale Stage a compiled glibc locale under /usr/lib/locale (list). Name it as glibc does, such as en_US.UTF-8. The data comes from a matching host directory under /usr/lib/locale. When there is none, localedef compiles the locale from /usr/share/i18n. The host locale-archive is never copied. Set LANG, LC_ALL, or a per-category LC_* entry with --env to select the locale. If nothing selects a staged locale, the pack warns. If a selector names a locale the pack did not stage, the pack warns as well.
symlinks --symlinks What a symlink you name becomes in the image: copy-all (default), preserve, copy-unsafe, or skip-unsafe. Covers the packed binary path and each add-file source. See Usage for the mode table.
include --include Force-stage extra libraries by soname or path, for example dlopen'd plugins (list).
nss --nss Name-service (NSS) modules to stage for glibc name lookups: files, dns, or none (list). Fewer modules trim CVE surface. A mode without files also drops /etc/passwd and /etc/group, which glibc reads through the files module. Default: files and dns.
deny --deny If this library ships, the pack fails (list). Resolved libraries, the loader, and NSS modules are all in scope, matched by soname or staged file name. This is a CI policy gate. Read sonames from scratchsmith graph.
require --require If this library does not ship, the pack fails (list). Same scope as deny.
sign --sign cosign-sign the pushed image (keyless, by digest). Requires a push target.
push --push Push the image straight to this registry reference, daemonless.
max-size --max-size If the packed image exceeds this size, the pack fails. The packed image is the fully-staged rootfs: payload + NSS includes + runtime extras. Write the size as 12MB, 512KiB, or a bare byte count (K/M/G are ×1000, Ki/Mi/Gi are ×1024).
runtime --runtime Container engine for the default load sink and the --smoke run: docker (default), podman, or nerdctl. The daemonless sinks (--oci-archive, --push) never invoke a runtime, so this is ignored there.

A full config file, and how to run it:

# scratchsmith.toml — loaded with `scratchsmith pack --config scratchsmith.toml`.
binary = "./dist/app"
entrypoint = "/app"
cmd = ["--serve"]
env = ["LANG=C.UTF-8"]
workdir = "/data"
user = "65532:65532"
label = ["role=api"]
healthcheck = ["/app", "--health"]
strip = true
upx = true
smoke = true
sbom = true
sbom-file = "sbom.json"
sbom-format = "cyclonedx-json"
scan = true
scan-fail-on = "high"
ca-certs = true
tz = true
init = true
add-file = ["./app.conf:/etc/app/app.conf", "/etc/motd"]
locale = ["en_US.UTF-8"]
symlinks = "copy-all"
include = ["libnss_myhostname.so.2"]
nss = ["files", "dns"]
deny = ["libssl.so.3"]
require = ["libseccomp.so.2"]
sign = true
push = "ghcr.io/you/app:latest"
max-size = "50MB"
runtime = "docker"
scratchsmith pack --config scratchsmith.toml                 # binary + all keys come from the file
scratchsmith pack --config scratchsmith.toml --push ghcr.io/you/app:dev ./other   # CLI overrides binary + push

The delivery sinks --oci-archive <file> and --no-build / --output <dir>, and the display-only --format, stay command-line-only. They are not config keys.

Profiles

Group keys under [profile.<name>] and pick one with --profile <name> (which requires --config). A profile layers over the base config, so shared keys live at the top level and per-environment overrides go in the profile:

binary = "./dist/app"
strip = true

[profile.ci]                    # scratchsmith pack --config scratchsmith.toml --profile ci
sbom = true
sign = true
push = "ghcr.io/you/app:latest"

Values layer in this order, last wins: base config → the selected [profile.<name>] → any command-line flag. Booleans OR together, so a profile can switch something on but not off. A scalar is replaced by the more specific layer. A non-empty list replaces the one beneath it.