Skip to main content

Pillars delivery and release configuration

ReferencePublicInterface: GitLab CI/CD

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.

SettingDefault and use
HELM_CHART_DIRchart; directory containing Chart.yaml.
HELM_REGISTRY${CI_REGISTRY}; destination hostname.
HELM_REGISTRY_USER, HELM_REGISTRY_PASSWORDGitLab registry credentials; included in the shared Docker authentication configuration.
HELM_REGISTRY_PATHEmpty; resolves to the lowercased project path followed by /helm/.
HELM_CHART_VERSIONUnset; uses the commit tag, otherwise the version in Chart.yaml followed by -CI_PIPELINE_ID.
HELM_CHART_TAGDeclared 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.

SettingDefault or requirementMeaning
KUSTOMIZE_FILES**/kustomization.yamlShell-expanded file patterns. Use explicit whitespace-separated paths when predictable selection matters.
KUSTOMIZE_IMAGE_UPDATEUnset; requires "true"Enables update-kustomize-image.
KUSTOMIZE_IMAGE_NAMEForwarded as ${REGISTRY}/${IMAGE} by the downstream triggerMatches an image's name or newName. Set explicitly for a standalone update pipeline.
KUSTOMIZE_IMAGE_TAGForwarded as ${IMAGE_TAG} by the downstream triggerValue written to newTag. Set explicitly for a standalone update pipeline.
KUSTOMIZE_PROJECTRequired for the downstream triggerTarget GitLab project path.
KUSTOMIZE_BRANCHRequired for the downstream trigger; ${CI_COMMIT_REF_NAME} inside the update jobTarget branch.
KUSTOMIZE_TRIGGER_IMAGE_UPDATEUnset; "false" disablesControls trigger-update-kustomize-image.
GIT_PUSH_OPTIONS-o ci.skip inside the update jobPush options; the default skips a pipeline caused by the push.
GIT_COMMIT_MESSAGEci: update image tag in kustomization filesCommit 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.

warning

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:

KeyExample value
KUSTOMIZE_PROJECTyour-org/application-manifests
KUSTOMIZE_BRANCHintegration

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.

SettingDefault or requirement
HELM_IMAGE_UPDATEUnset; "true" enables the target update.
HELM_PROJECT, HELM_BRANCHTarget 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_MESSAGEci: 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:

  1. ZARF_PACKAGE_FILE when set.
  2. zarf.yaml or zarf.gen.yaml under a non-default ZARF_PACKAGE_DIR.
  3. Root zarf.yaml or zarf.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.

SettingDefault 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_PASSWORDGitLab registry credentials; must permit staging and release access.
ZARF_REGISTRY_PATHEmpty; defaults to lowercased ${CI_PROJECT_PATH}/zarf.
ZARF_STAGING_REGISTRY_PATHEmpty; defaults to lowercased ${CI_PROJECT_PATH}/cache.
ZARF_ARCHITECTUREEmpty; select the target architecture through Zarf configuration or a matrix. Built packages must report amd64 or arm64.
ZARF_PACKAGE_CREATE_FLAVORUnset; native Zarf flavor selection.
ZARF_FLAVOREmpty; compatibility alias that overrides ZARF_PACKAGE_CREATE_FLAVOR when populated.
ZARF_OCI_CONCURRENCYEmpty; 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_DIRzarf-package-build; each build stores records below its own job-ID directory.
ZARF_CONFIGUnset; otherwise an existing config file. When unset, discovers zarf-config.yaml, .yml, or .toml beside the definition.
ZARF_PLAIN_HTTP, ZARF_INSECURE_SKIP_TLS_VERIFYUnset; 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​

JobActivationOutput or behavior
software-bill-of-materialDisabled 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-evidenceDisabled by BODY_OF_EVIDENCE: "false"; same release-pipeline and tag rules.Archives nonempty directories named evidence.
semantic-releaseManual and nonblocking when the ref name matches SEMANTIC_RELEASE_REF_REGEX.Runs semantic-release with the selected preset, project configuration, token, and arguments.
attach-artifactsDisabled 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​

SettingDefault and use
GITLAB_TOKEN${CI_JOB_TOKEN} in semantic-release and GoReleaser publication.
RELEASE_TOKENUnset; 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_REGEXMatches 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_KEYUnset; base64-encoded GPG private key for the optional signing path.
SEMANTIC_RELEASE_AUTO_SIGN_COMMITSUnset; "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 settingMeaning
TARGET_GITLAB_HOSTDestination GitLab hostname.
TARGET_PROJECT_PATHDestination namespace path; the script appends ${CI_PROJECT_NAME}.git.
TARGET_USERNAME, TARGET_TOKENDestination account and credential with the required push/MR permissions.
SYNC_TARGETSelects target-specific files under config and a matching changelog suffix.
REPO_PREFIXPrefix removed from the source project path for derived names/logging.
SMOOTHGLUE_GIT_BOT_USERNAME, SMOOTHGLUE_GIT_BOT_GPG_EMAILSigning identity.
SMOOTHGLUE_GIT_BOT_GPG_PRIVATE_KEY, SMOOTHGLUE_GIT_BOT_GPG_PASSPHRASEBase64-encoded signing material.
SYNC_IMAGEImage 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.