Reference for the command. It is what the GitHub Action (github-action.md) runs, and what a person runs by hand. The bytes it writes are format-v2.md's; this page is only the interface.
settings-image build <folder> [--oci-layout <dir>] [flags]
settings-image push <folder> --repository <registry>/<path> [flags]
settings-image versionbuildrenders the folder and builds the image. It writes nothing anywhere unless--oci-layout <dir>is given, in which case it writes an OCI image layout directory (oras-go'soci.Store) holding the image and its provenance, tagged aspushwould. A refusal is reported exactly aspushreports it. This is the dry run, and how the conformance corpus is written.pushdoesbuildand then pushes, in format-v2.md §10's order.versionprints the build (exists).
| Flag | build |
push |
Meaning |
|---|---|---|---|
--repository <host/path> |
required | where to push, without a tag: ghcr.io/configbutler/card-reveal-config |
|
--tag <tag> (repeatable) |
yes | yes | an extra tag. Must be a valid OCI tag; refused otherwise, never rewritten |
--branch-tag <branch> |
yes | yes | add the sanitised branch tag (format-v2.md §10); pushed last |
--source <url> |
yes | yes | the repository URL for org.opencontainers.image.source and provenance; overrides detection (§3) |
--sensitive-kind <group/Kind> (repeatable) |
yes | yes | a kind under resources/ that must be SOPS-encrypted like a Secret (format-v2.md §4); Kind alone for the core group. A Secret must always be SOPS-encrypted. Whether a ConfigMap becomes files is not a flag: it is an annotation on the ConfigMap (format-v2.md §5) |
--no-provenance |
yes | yes | build no referrer and no sha- tag, and skip Git entirely (also allows a folder outside Git); the source then comes from --source or GITHUB_* only |
--allow-dirty |
yes | yes | build a folder with uncommitted changes; implies no provenance and no sha- tag |
--allow-shallow |
yes | yes | accept a shallow clone (§3); provenance then names HEAD and says so in a warning |
--plain-http |
yes | for a local registry without TLS | |
--username, --password-stdin |
yes | registry credentials (§4) | |
--registry-token-stdin |
yes | a bearer token for the registry, sent unchanged (§4) | |
--timestamp-tag <branch> |
yes | yes | add <branch>-<unix committedAt>-<sha7>, a tag that never moves and sorts by commit time (format-v2.md §10); what Flux follows (flux.md). Needs the commit that last changed the folder: a usage error with --no-provenance, --allow-dirty or --allow-shallow |
--provenance-author none|name|full |
yes | yes | how much of the commit author goes into provenance (format-v2.md §9); default none |
--json |
yes | yes | print the result as JSON on stdout (§5) |
The folder must be inside a Git work tree unless --no-provenance is set. The CLI shells out to
git; it does not link a Git library.
-
Refuse a dirty folder:
git status --porcelain --ignored -- <folder>/resources <folder>/rawmust be empty, unless--allow-dirty. Otherwise the image would hold bytes no commit holds, while the provenance names a commit. Ignored files count: the CLI reads the folder from disk, so a file.gitignorehides would still reach the image. -
The commit that last changed the folder:
git log -1 --format=%H%x00%an%x00%ae%x00%s%x00%cI -- <folder>/resources <folder>/raw. Using this commit rather than HEAD means a rebuild on a later commit that did not touch the folder is a complete no-op: the same digest, the same referrer and the samesha-tag.Both commands look only at
resources/andraw/, the parts that reach the image (format-v2.md §2). A known top-level file such as.gittargetignoreorREADME.mdis skipped, so editing it changes neither the digest nor the commit, and an uncommitted edit to it does not make the folder dirty. -
Refuse a shallow clone (
git rev-parse --is-shallow-repositoryistrue) unless--allow-shallow. In a depth-1 clone,git log -- <folder>returns HEAD for every folder, because the boundary commit appears to add everything. The answer would be wrong without any error. The Action's documented checkout isfetch-depth: 0. -
Repository URL, the first that applies:
--source;$GITHUB_SERVER_URL/$GITHUB_REPOSITORYwhen both are set;git remote get-url origin, normalised: userinfo removed,git@host:o/rbecomeshttps://host/o/r, a trailing.gitdropped;- none, in which case the annotation and the field are omitted.
-
Ref:
$GITHUB_REFwhen set, otherwisegit symbolic-ref -q HEAD, otherwise omitted. -
Path: the folder relative to
git rev-parse --show-toplevel, slash-separated.
The CLI knows the generic forms of registry credential and nothing about any one registry. A cloud's own token exchange (ECR, ACR, Artifact Registry) belongs to that cloud's login action, which leaves its result in the Docker config.
| Credential | Sent as | For example |
|---|---|---|
| a username and a password | HTTP Basic, or exchanged at the registry's token service | ghcr.io, Docker Hub, Harbor, Quay; a token that a registry takes as a password |
| a registry token | Authorization: Bearer <token>, unchanged |
zot accepting a GitHub Actions OIDC token; any registry that takes a bearer token as is |
| the Docker config and its credential helpers | whatever docker login stored |
ECR, ACR and Artifact Registry through their login actions; docker/login-action |
| none | anonymous | a local registry |
In this order, the first that is given:
--usernamewith--password-stdin, or--registry-token-stdin;SETTINGS_IMAGE_USERNAMEandSETTINGS_IMAGE_PASSWORD, orSETTINGS_IMAGE_REGISTRY_TOKEN, from the environment (what the Action sets);- the Docker config and its credential helpers, as
docker loginleft them (oras-go'scredentials.NewStoreFromDocker), sodocker/login-actionworks. Itsregistrytokenfield is a registry token, so writing a token there works today, without the flag; - anonymous.
A run uses one credential. A registry token together with --username or --password-stdin (or
their variables) is a usage error (exit 2), never a guess. In the Go API the token is
RepositoryOptions.RegistryToken.
A registry token is sent as Authorization: Bearer <token> on every request to the registry's
own host, from the first request on, whatever challenge the registry would answer with. That is
what crane does: go-containerregistry sends a registry token as Bearer even to a Basic
challenge. oras-go alone sends a token only to a Bearer challenge, so the CLI sets the header
itself, in a transport that knows the registry host.
Never to another host. A blob request may be redirected to storage (S3, a CDN); the token stops at the registry. The transport compares the request's host and port with the registry's, and sends nothing to any other.
A registry that refuses the credential (HTTP 401 or 403) exits 4 (§6), naming the registry and never the credential.
A credential is never printed, logged or put in an error. A 401 or 403 from the registry is its own error, naming the registry and saying the credential was refused.
Progress and warnings go to stderr. With --json, stdout carries exactly one JSON object:
{
"repository": "ghcr.io/configbutler/card-reveal-config",
"digest": "sha256:80b52437…",
"reference": "ghcr.io/configbutler/card-reveal-config@sha256:80b52437…",
"tags": ["sha-0123456", "main"],
"provenance": "sha256:8962065a…",
"commit": "0123456789abcdef0123456789abcdef01234567",
"skipped": [".gittargetignore"],
"changed": true
}provenanceandcommitarenullwhen there are none.skippedlists the files at the top of the folder that were left out (format-v2.md §2), each also named in a warning on stderr. It is[]when there are none.changedisfalsewhen, before the push, the image already existed in the repository and every tag already pointed at it. That is the "nothing to do" case, and the Action reports it.buildgives the same object withoutchanged, and withrepositoryset to--repositoryif given.
Without --json, stdout carries only the reference, so ref=$(settings-image push …) works.
| Code | Meaning |
|---|---|
| 0 | built (and pushed), or nothing to do |
| 1 | an error that a retry might fix: network, registry, Git |
| 2 | usage: unknown command or flag, missing argument |
| 3 | refused: the folder breaks a rule of format-v2.md §8, or a tag is invalid, or the folder is dirty, outside Git (without --no-provenance), or the clone shallow. Retrying cannot help; the message names the path |
| 4 | the registry refused the credential (HTTP 401 or 403): a wrong or expired token, a missing trust rule, no push permission. Retrying cannot help; the message names the registry. |
- No decryption, ever, and no keys. SOPS-encrypted files are checked, not decrypted.
- No registry management. Creating the repository, setting visibility and retention are the registry's business.
- No watching. Following a Git source continuously is the controller's job (phase 3).