Pillars delivery and release configuration
Delivery outputs have separate activation rules. A container or staging language package can already exist before analysis finishes. A Helm chart, Zarf release, evidence archive, and GitLab release attachment are not interchangeable outputs.
Helm chart packages
helm-chart runs in artifact-build when Helm support is included and
HELM_CHART_DIR/Chart.yaml exists, unless HELM_CHART: "false".
HELM: "false" excludes the Helm integration. The job downloads chart dependencies,
checks helm template, packages the chart, and immediately pushes it to its OCI registry.
It uses the shared context failure rules, not an unconditional release gate.
| Setting | Default and use |
|---|---|
HELM_CHART_DIR | chart; directory containing Chart.yaml. |
HELM_REGISTRY | ${CI_REGISTRY}; destination hostname. |
HELM_REGISTRY_USER, HELM_REGISTRY_PASSWORD | GitLab registry credentials; included in the shared Docker authentication configuration. |
HELM_REGISTRY_PATH | Empty; resolves to the lowercased project path followed by /helm/. |
HELM_CHART_VERSION | Unset; uses the commit tag, otherwise the version in Chart.yaml followed by -CI_PIPELINE_ID. |
HELM_CHART_TAG | Declared empty but not read by the chart job. Use HELM_CHART_VERSION. |
HTTP/HTTPS and OCI dependency repository URLs are recognized. Other repository forms,
including a file:// entry in the current loop, cause an error. Configure authentication
for every private dependency registry in addition to the publication registry.
The artifact is helm-chart/package/CHART_NAME-CHART_VERSION.tgz with the default
paths, and helm.env records the resolved registry path.
For a chart outside chart, set the project CI/CD variable HELM to true, then
merge this path into your application pipeline:
variables:
HELM_CHART_DIR: "deploy/chart"
Kustomize image updates
Kustomize support includes kustomize-build, which renders the selected Kustomizations,
and kustomize-kubeconform, which checks Kustomization and rendered-manifest schemas.
KUSTOMIZE: "false" excludes the integration. KUSTOMIZE_BUILD: "false" disables
both build and schema-validation jobs; KUSTOMIZE_KUBECONFORM: "false" disables only
schema validation. The latter requires kustomize-build artifacts.
| Setting | Default or requirement | Meaning |
|---|---|---|
KUSTOMIZE_FILES | **/kustomization.yaml | Shell-expanded file patterns. Use explicit whitespace-separated paths when predictable selection matters. |
KUSTOMIZE_IMAGE_UPDATE | Unset; requires "true" | Enables update-kustomize-image. |
KUSTOMIZE_IMAGE_NAME | Forwarded as ${REGISTRY}/${IMAGE} by the downstream trigger | Matches an image's name or newName. Set explicitly for a standalone update pipeline. |
KUSTOMIZE_IMAGE_TAG | Forwarded as ${IMAGE_TAG} by the downstream trigger | Value written to newTag. Set explicitly for a standalone update pipeline. |
KUSTOMIZE_PROJECT | Required for the downstream trigger | Target GitLab project path. |
KUSTOMIZE_BRANCH | Required for the downstream trigger; ${CI_COMMIT_REF_NAME} inside the update job | Target branch. |
KUSTOMIZE_TRIGGER_IMAGE_UPDATE | Unset; "false" disables | Controls trigger-update-kustomize-image. |
GIT_PUSH_OPTIONS | -o ci.skip inside the update job | Push options; the default skips a pipeline caused by the push. |
GIT_COMMIT_MESSAGE | ci: update image tag in kustomization files | Commit message for the update. |
The trigger is a manual, nonblocking publish job when target project and branch are
set. It starts a downstream pipeline with the update settings and uses GitLab's mirror
strategy. The target must already include Kustomize support and permit the initiating
user and relevant job token to perform the required operations.
The update job commits and pushes directly to the selected target branch. It does not open a merge request. Review the image, target branch, and the environment that consumes those manifests before starting the job.
For the downstream include, add these non-secret project CI/CD variables in the application project, replacing the fictional destination:
| Key | Example value |
|---|---|
KUSTOMIZE_PROJECT | your-org/application-manifests |
KUSTOMIZE_BRANCH | integration |
Set the update behavior on the named trigger in .gitlab-ci.yml. Replace the example
image and tag with a verified image already published by your application pipeline:
trigger-update-kustomize-image:
variables:
KUSTOMIZE_IMAGE_UPDATE: "true"
KUSTOMIZE_FILES: "overlays/dev/kustomization.yaml"
KUSTOMIZE_IMAGE_NAME: "registry.example.com/example/application"
KUSTOMIZE_IMAGE_TAG: "1.2.3"
The trigger inherits only KUSTOMIZE and KUSTOMIZE_TRIGGER_IMAGE_UPDATE from
top-level YAML variables. Other YAML defaults, including the update switch, file paths,
and shared image defaults, are excluded. Use the trigger's variables map for these
values. Its project and branch must still be available before include selection.
The shared trigger does not declare a dependency that imports container-image's
image.env. Do not assume that the build's derived image name and tag are forwarded.
Use explicit known coordinates, as above, or a platform-provided workflow that has
verified the transfer of build output. See GitLab's
downstream variable behavior.
Verify the downstream job log, the actual Git diff, and the resulting target commit. A successful update does not prove that a deployment controller applied the manifests or that the application is healthy.
The current same-repository update job names container-build in an optional dependency,
while the image job is named container-image. Do not rely on it to wait for that image
job. Use explicit known image coordinates for a standalone update, or have the platform
team supply a corrected and tested dependency configuration before using same-pipeline
automatic image values. Glob expansion and output-directory handling also need validation
for nested Kustomizations; explicit paths do not provision missing application resources.
Helm values updates
update-helm-image edits selected values files and pushes a commit. It requires Helm
support to be included and HELM_IMAGE_UPDATE: "true".
trigger-update-helm-image is manual and nonblocking when the target project and branch
are configured, unless HELM_TRIGGER_IMAGE_UPDATE: "false" or Helm is excluded.
| Setting | Default or requirement |
|---|---|
HELM_IMAGE_UPDATE | Unset; "true" enables the target update. |
HELM_PROJECT, HELM_BRANCH | Target project and branch for the trigger; the update job alone defaults its branch to ${CI_COMMIT_REF_NAME}. |
HELM_IMAGE_NAME, HELM_IMAGE_TAG | ${REGISTRY}/${IMAGE} and ${IMAGE_TAG} in the update job and trigger. |
HELM_FILES | **/values.yaml; use explicit paths to constrain the change. |
HELM_YQ_EXPRESSION | .image.tag = strenv(HELM_IMAGE_TAG). |
GIT_PUSH_OPTIONS | -o ci.skip. |
GIT_COMMIT_MESSAGE | ci: update image tag in helm values. |
The default expression changes only .image.tag; it does not update the repository
field or use HELM_IMAGE_NAME as a selector. Set the expression for your chart's
actual values structure. Because the trigger declares its own expression default,
override the trigger's named-job variable as well when forwarding a custom expression.
Set HELM_PROJECT and HELM_BRANCH as project CI/CD variables for include selection.
As with Kustomize, this workflow pushes directly to a branch and does not create a merge
request. Git push permissions, branch protection, and downstream access must allow it.
The Helm trigger disables inheritance of all top-level YAML variables and does not declare a dependency that imports the image build's dotenv output. Configure the update switch, files, and verified image coordinates on the named trigger. For example, after setting the target project and branch, replace the sample image and tag:
trigger-update-helm-image:
variables:
HELM_IMAGE_UPDATE: "true"
HELM_FILES: "environments/dev/values.yaml"
HELM_IMAGE_NAME: "registry.example.com/example/application"
HELM_IMAGE_TAG: "1.2.3"
HELM_YQ_EXPRESSION: ".image.tag = strenv(HELM_IMAGE_TAG)"
Confirm that the named values file uses this structure and that its existing image repository matches the verified image. The default expression changes only the tag.
Zarf packages
Pillars supplies zarf-package-build, zarf-package-scan, and zarf-package-publish.
These operate on package definitions in your application project; they do not install
the package into a cluster.
Automatic selection runs on tags or the default branch when a definition exists. The definition is chosen in this order:
ZARF_PACKAGE_FILEwhen set.zarf.yamlorzarf.gen.yamlunder a non-defaultZARF_PACKAGE_DIR.- Root
zarf.yamlorzarf.gen.yaml.
ZARF: "false" disables these jobs. ZARF: "true" enables them in other contexts,
including merge requests, and causes a missing definition to surface as a job failure.
There are no separate declared build/publish disable switches in this configuration.
GRYPE: "false" removes the Zarf scan as well as the common Grype image scan; the
remaining Zarf publication jobs are still selected.
| Setting | Default and use |
|---|---|
ZARF_PACKAGE_DIR, ZARF_PACKAGE_FILE | . and empty; directory or explicit definition. |
ZARF_REGISTRY | ${CI_REGISTRY}; hostname with optional port, without a URL scheme or repository path. |
ZARF_REGISTRY_USER, ZARF_REGISTRY_PASSWORD | GitLab registry credentials; must permit staging and release access. |
ZARF_REGISTRY_PATH | Empty; defaults to lowercased ${CI_PROJECT_PATH}/zarf. |
ZARF_STAGING_REGISTRY_PATH | Empty; defaults to lowercased ${CI_PROJECT_PATH}/cache. |
ZARF_ARCHITECTURE | Empty; select the target architecture through Zarf configuration or a matrix. Built packages must report amd64 or arm64. |
ZARF_PACKAGE_CREATE_FLAVOR | Unset; native Zarf flavor selection. |
ZARF_FLAVOR | Empty; compatibility alias that overrides ZARF_PACKAGE_CREATE_FLAVOR when populated. |
ZARF_OCI_CONCURRENCY | Empty; when set, must be a positive integer and is exported as ZARF_PACKAGE_OCI_CONCURRENCY. |
ZARF_CACHE_DIR | .cache/zarf; mapped to Zarf's cache path and cached between jobs. |
ZARF_BUILD_ARTIFACT_DIR | zarf-package-build; each build stores records below its own job-ID directory. |
ZARF_CONFIG | Unset; otherwise an existing config file. When unset, discovers zarf-config.yaml, .yml, or .toml beside the definition. |
ZARF_PLAIN_HTTP, ZARF_INSECURE_SKIP_TLS_VERIFY | Unset; fall back to the matching config-file values or false. Either enables insecure registry-tool access. Use only where the installation requires it. |
The build creates exactly one unsplit package archive per job with native SBOM generation enabled. It reads the built name, version, architecture, and flavor; stages the package in OCI; verifies its digest; and retains the package record and native JSON SBOMs for one day. It does not retain the full archive as a GitLab artifact.
The primary tag uses the Git tag, or built package version if there is no Git tag,
followed by -CI_PIPELINE_ID and an optional flavor suffix. Additional tags use that
base version, branch slug, and commit SHA, with the same flavor suffix. Publication
verifies that staged architecture/digest records match the OCI index, copies the index
to the release repository, and verifies the resulting digest. Jobs use resource groups
to serialize the relevant registry operations.
The scan runs Grype against each native SBOM with a default high threshold. It fails
on scan errors or threshold failures and logs its results. It declares no retained
scan artifacts. A package with no SBOM-covered payload can produce zero scans; that
log message is not evidence of a full payload vulnerability assessment.
Example for one architecture:
variables:
ZARF: "true"
ZARF_PACKAGE_DIR: "packages/application"
ZARF_ARCHITECTURE: "amd64"
To create multiple package variants, override the build job with parallel:matrix using
ZARF_ARCHITECTURE and ZARF_PACKAGE_CREATE_FLAVOR. The legacy plural variables
ZARF_ARCHITECTURES and ZARF_FLAVORS are explicitly rejected. Every matrix entry must
produce a distinct name/flavor/architecture record without conflicting tag assignments.
Use architectures and flavors supported by the package and your installation.
zarf-package-build:
parallel:
matrix:
- ZARF_ARCHITECTURE: ["amd64"]
ZARF_PACKAGE_CREATE_FLAVOR: ["build", "run"]
This example assumes that your definition implements both named flavors. If staged records or SBOM artifacts have expired, run a new build pipeline; retrying publication cannot reconstruct missing records. The build explicitly publishes staging content without a signing key. Do not infer a signed-package guarantee from digest verification.
Release and evidence jobs
| Job | Activation | Output or behavior |
|---|---|---|
software-bill-of-material | Disabled by SOFTWARE_BILL_OF_MATERIAL: "false"; automatic on success when RELEASE_PIPELINE is nonempty; otherwise manual and nonblocking on tags. | Archives nonempty directories named sbom as .tar.gz and .zip. |
body-of-evidence | Disabled by BODY_OF_EVIDENCE: "false"; same release-pipeline and tag rules. | Archives nonempty directories named evidence. |
semantic-release | Manual and nonblocking when the ref name matches SEMANTIC_RELEASE_REF_REGEX. | Runs semantic-release with the selected preset, project configuration, token, and arguments. |
attach-artifacts | Disabled by ATTACH_ARTIFACTS: "false"; otherwise requires a tag and exactly RELEASE_PIPELINE: "true". | Uploads the expected SBOM/evidence archives and available recognized packages to a GitLab release. Requires RELEASE_TOKEN. |
RELEASE_PIPELINE has no global default. Set it to "true" only for your intended
release workflow; leave it unset otherwise. The value "false" still selects automatic
archive creation because those jobs test whether it is nonempty.
Archive names and retention
SBOM_OUTPUT_PREFIX defaults to sbom-${CI_PROJECT_NAME}-${CI_COMMIT_SHORT_SHA};
BOE_OUTPUT_PREFIX defaults to boe-${CI_PROJECT_NAME}-${CI_COMMIT_SHORT_SHA}.
Each receives .zip and .tar.gz suffixes. Attachment currently uses these default
filenames literally, so changing a prefix without adapting attachment breaks that flow.
Keep the defaults when using the standard attachment job.
The archive jobs collect downloaded upstream artifacts, not every file ever produced by earlier pipelines. Empty input directories may leave no ZIP to attach. Most jobs use the GitLab instance/project retention default; ClamAV explicitly retains its report for 30 days, GoReleaser build output and Zarf build records for one day, and the generated npm child configuration for one hour. Configure longer retention in your project where required, and retrieve evidence before it expires.
Release permissions and settings
| Setting | Default and use |
|---|---|
GITLAB_TOKEN | ${CI_JOB_TOKEN} in semantic-release and GoReleaser publication. |
RELEASE_TOKEN | Unset; secret project access token with API access and permissions for attachment or the optional signing workflow. |
SEMANTIC_RELEASE_ARGS | --extends @smoothglue/pillars-config in the common configuration; framework includes can select a framework-specific preset. |
SEMANTIC_RELEASE_TAG_FORMAT | $${version}; escaped for GitLab, resulting in a version-only tag format for semantic-release. |
SEMANTIC_RELEASE_REF_REGEX | Matches main, master, next, next-major, beta, alpha, and numeric maintenance refs such as 1.x or 1.2.x. See the exact expression in the defaults catalog. |
SEMANTIC_RELEASE_GPG_KEY | Unset; base64-encoded GPG private key for the optional signing path. |
SEMANTIC_RELEASE_AUTO_SIGN_COMMITS | Unset; "true" requests a temporary GPG key when a supplied key is absent. |
The optional signing path requires RELEASE_TOKEN, looks up that token's user, registers
a public GPG key, enables commit signing, and switches GITLAB_TOKEN to that token.
The ordinary path uses the job token. Git push permissions and branch protection must
permit the operations performed by the release configuration.
Framework presets are @smoothglue/pillars-config-npm, -gradle, -python, and
-dotnet for their respective integrations; Go and CMake have no dedicated override
in the documented framework files. Multiple selected frameworks can override the same
preset setting. Set SEMANTIC_RELEASE_ARGS on semantic-release explicitly when a
project needs a known combined release configuration. The selected preset must be
available in the approved image; a matching branch alone does not guarantee a new
release if there are no releasable changes.
The attachment job is separate from release creation. It uploads the four expected archives, plus recognized Gradle JARs, cached container archives, Helm packages, Poetry packages, npm tarballs, and CMake packages. It does not enumerate pip or .NET package directories. Registry publication and release attachment therefore have different coverage. Ensure the target release and expected artifacts exist before expecting attachment to succeed.
Optional repository synchronization
Pillars includes .target-sync-template, a hidden job template. It does not create an
active sync job on its own. It is a site-specific repository transformation workflow,
not application deployment or the Helm/Kustomize update mechanism.
| Required setting | Meaning |
|---|---|
TARGET_GITLAB_HOST | Destination GitLab hostname. |
TARGET_PROJECT_PATH | Destination namespace path; the script appends ${CI_PROJECT_NAME}.git. |
TARGET_USERNAME, TARGET_TOKEN | Destination account and credential with the required push/MR permissions. |
SYNC_TARGET | Selects target-specific files under config and a matching changelog suffix. |
REPO_PREFIX | Prefix removed from the source project path for derived names/logging. |
SMOOTHGLUE_GIT_BOT_USERNAME, SMOOTHGLUE_GIT_BOT_GPG_EMAIL | Signing identity. |
SMOOTHGLUE_GIT_BOT_GPG_PRIVATE_KEY, SMOOTHGLUE_GIT_BOT_GPG_PASSPHRASE | Base64-encoded signing material. |
SYNC_IMAGE | Image with the tools and privileges needed by the template's setup. |
There are no declared defaults for the required target/signing inputs. The template
installs additional tools over the network, transforms target-specific npm or Python
files, and handles changelogs. The special cbc2 target also rewrites history to remove
other target changelogs. Default-branch synchronization force-pushes a temporary branch
and requests a merge request with auto-merge; other branches are force-pushed directly.
The current script also logs a credential-bearing target URL. This template needs a
platform-owned correction and reviewed target configuration before customer activation;
masking alone is not a substitute for removing that output. Do not extend it as a
generic sync recipe.