Skip to content

Publish

publish: declares what StageFreight does with a build — push image tags, sync project identity, publish a GitLab component, cut a forge release, publish an archive or package, or deploy a static site. It's an id → target map; each entry is a discriminated union keyed by kind — the kind decides which other keys are valid.

Every target shares a when: routing block (all non-empty conditions must match — AND logic; see Patterns & conditions). The map key is the target's id.

Registry — push image tags

Pushes a build's image to a container registry under one or more tags:.

publish:
  dockerhub-stable:
    kind: registry
    build: myapp                       # references a builds[] id
    registry: dockerhub                # references a registries[] id — url/provider/creds/path resolve from it
    tags: ["{version}", "{major}.{minor}", "latest"]
    when:
      git_tags: [stable]               # a named pattern from git.tags:
      events: [tag]
    retention: { keep_last: 10, keep_monthly: 6 }

registry: references a registries[] id (see Identity & Connectivity); pass a list (registry: [dockerhub, ghcr, harbor]) to fan the target across several registries at once. tags template expansion and retention are cross-cutting — see Concepts.

Project Metadata — sync identity to registries & forge repos

kind: metadata pushes a project's identity — description, long readme/overview, website, topics, logo — to every registry and forge repo destination that can hold it, from one source of truth. Each field maps to what a destination supports and is silently skipped where absent; nothing is truncated, and org/namespace fields are never touched.

publish:
  project-meta:
    kind: metadata
    registry: [dockerhub, harbor]        # registry pages
    repos: [github-mirror, primary]      # forge project pages
    description:                         # length-tiered variants — the engine fits each destination's cap
      - "The full, richest tagline your most generous destination can hold…"
      - "A trimmer version for tighter caps…"
      - "A punchy fallback that fits anywhere"
    readme: README.md                    # long markdown body (registries with an overview field)
    website: https://example.com         # external site URL (forges that have the field)
    topics: [ci-cd, gitops, kubernetes]  # discovery tags (forges) — normalized to lowercase-hyphenated
    logo: assets/logo.png                # project avatar (project-scoped forge avatars)
    when: { branches: [main], events: [push, tag] }

What each destination supports

destination description (short) readme (long body) website topics logo
Docker Hub ✓ ~100 (word-truncated) ✓ Overview (~25k) ✕ ✕ ✕ (org-scoped)
Harbor — (single field) ✓ Info (markdown) ✕ ✕ ✕
Quay — (single field) ✓ Description (markdown) ✕ ✕ ✕
JFrog ✓ config description ✕ (no README API) ✕ ✕ ✕
GHCR / GitLab CR ✕ (no description API) ✕ ✕ ✕ ✕
GitHub ✓ ~350 ✕ (README is committed) ✓ ✓ ✕ (org-scoped)
GitLab ✓ ✕ (committed) ✕ (no field) ✓ ✓ project avatar
Gitea / Forgejo ✓ ✕ (committed) ✓ ✓ ✓ project avatar

Behavior: single-field registries (Harbor, Quay) take the readme in their one markdown field (falling back to the short description if no readme); the engine fits the longest description variant to each destination's cap and warns rather than truncating; logo syncs idempotently (re-uploads only on change) and is skipped where the avatar is org-scoped. Each run reports per-destination what was set / skipped / warned.

Release — cut forge releases

Creates a release on the detected forge, with rolling git-tag aliases that track it.

publish:
  primary-release:
    kind: release
    aliases: ["{version}", "{major}.{minor}", "latest"]
    retention: { keep_last: 10, keep_monthly: 6 }
    when: { git_tags: [stable], events: [tag] }

The aliases are rolling git tags resolved with the same template variables as everything else — {version} → 1.2.3, {major}.{minor} → 1.2, latest → always the newest.

Cutting the tag & its release notes

Releases are cut with stagefreight tag (never raw git tag — that is hook-blocked):

stagefreight tag v1.2.3 -m "- first highlight
- second highlight"
  • The -m message is the release's Notable Features section — each bulleted line becomes one item in that list. Write the highlights you want to surface; StageFreight renders them under the heading and assembles the rest of the notes (Image Availability, Downloads + checksums, Security summary, SBOM/signatures) automatically.
  • Omit -m and the glossary pipeline generates the highlights from the conventional-commit history since the previous release. Interactive (TTY) runs prompt for the message; non-interactive runs require -m or a non-empty glossary result.
  • --target <ref> tags a specific commit without checking it out (e.g. release origin/main while on another branch); a [skip ci] tip is skipped and its releasable parent is tagged instead.
  • --dry-run previews the full plan and the rendered highlights; --push creates and pushes the tag, which triggers the release pipeline. Without --push the tag stays local.

Bullets with backticks or apostrophes

Passing them straight through a shell lets it interpret them. Keep the notes in a file and pass -m "$(cat NOTES.md)" — command-substitution output is not re-parsed.

CLI

Release authoring (release create, release notes, release prune, release badge) and its flags live in the CLI Reference. In CI these run as part of the publish phase; you rarely invoke them by hand.

Other target kinds

gitlab-component (GitLab CI/CD component publish), archive / package distribution (kind: generic-package), and pages (static-site deploy, e.g. these docs to Cloudflare Pages) are declared the same way — a kind, a build: or source, and a when:. Their full field sets are in the generated reference below.

Reference

The blocks below are generated from the config source — for each kind, exactly the fields it accepts, with each field's meaning, allowed values, and whether it's required.

publish

Targets defines distribution targets and side-effects. Declared under the publish: key as an id→target map (execution order preserved).

kind: registry

publish:
  <id>:   # entry key = the unique id
    kind: registry   # Kind is the target type. Determines which fields are valid. · required
    registry: [<string>]   # Registry references registries[].id for registry/metadata targets. Accepts a single id (registry…
    build: <string>   # Build references a BuildConfig.ID. Required for kind: registry.
    tags: [<string>]   # Tags are tag templates resolved against version info (kind: registry). e.g., ["{version}"…
    signing_profile: <string>   # SigningProfile references a signing.profiles[].id — the trust profile this target signs under.…
    native_scan: false   # NativeScan enables post-push vulnerability scanning via the registry's own built-in scanner.…
    retention:   # Retention controls cleanup of old tags/releases. Structured only in v2 (no scalar shorthand).
      keep_last: <int>   # keep the N most recent tags per series (0/-1/unset = ∞) · required
      keep_daily: <int>   # keep one per day for the last N days · required
      keep_weekly: <int>   # keep one per week for the last N weeks · required
      keep_monthly: <int>   # keep one per month for the last N months · required
      keep_yearly: <int>   # keep one per year for the last N years · required
      keep_branches: <int>   # keep the N most-recent identity groups per template (bounds retired branches; 0/unset = ∞) · required
      max_age: <string>   # keep items newer than this ("14d", "72h"); additive like keep_* — alone it means "only the recent… · required
      identity: [<string>]   # extra identity vars beyond the {branch}/{env} defaults — partition tags into independent series · required
      protect: [<string>]   # tag patterns that are never deleted, an explicit override · required
      refs:   # scoped overrides: a different policy for items matching a pattern (first match wins; unset fields… · required
        - match: <string>   # pattern (same syntax as protect/templates) · required
          keep_last: <int>   # required
          keep_daily: <int>   # required
          keep_weekly: <int>   # required
          keep_monthly: <int>   # required
          keep_yearly: <int>   # required
          max_age: <string>   # required
    when:   # When specifies routing conditions for this target: a single condition-set, or a list of them (OR…
      - branches: [<string>]   # Branches lists branch filters. Each entry is a policy name or "re:<regex>". Empty = no branch…
        git_tags: [<string>]   # GitTags lists git tag filters. Each entry is a policy name or "re:<regex>". Empty = no tag…
        events: [<string>]   # Events lists CI event type filters. Supported: push, tag, release, schedule, manual, pull_request…
        forges: [<string>]   # Forges restricts this target to specific CI forges by provider name (github, gitlab, gitea…
        outcomes: [<string>]   # Outcomes gates on the RUN outcome (success | failure | warning) — the notification dimension of…
        signals: [<string>]   # Signals gates on things the run FOUND, independent of its outcome — a run that succeeded can…
        schedules: [<string>]   # Schedules restricts this condition to runs fired by one of these schedules (ids from schedules:).…

kind: metadata

publish:
  <id>:   # entry key = the unique id
    kind: metadata   # Kind is the target type. Determines which fields are valid. · required
    registry: [<string>]   # Registry references registries[].id for registry/metadata targets. Accepts a single id (registry…
    repos: [<string>]   # Repos names the destination repos[].id for a kind: release target. The repo with role primary is…
    description: [<string>]   # Description is the short project description/tagline. A scalar for kind: registry; kind: metadata…
    readme: <string>   # Readme is the long markdown 'project page' body (registries only: Docker Hub Overview, Harbor Info…
    website: <string>   # Website is the project's external site URL (forges: GitHub, Gitea/Forgejo).
    topics: [<string>]   # Topics are discovery tags (forges: GitHub, GitLab, Gitea). Authored freely; the engine normalizes…
    logo: <string>   # Logo is a path to the project avatar image (project-scoped forges: GitLab, Gitea/Forgejo); synced…
    when:   # When specifies routing conditions for this target: a single condition-set, or a list of them (OR…
      - branches: [<string>]   # Branches lists branch filters. Each entry is a policy name or "re:<regex>". Empty = no branch…
        git_tags: [<string>]   # GitTags lists git tag filters. Each entry is a policy name or "re:<regex>". Empty = no tag…
        events: [<string>]   # Events lists CI event type filters. Supported: push, tag, release, schedule, manual, pull_request…
        forges: [<string>]   # Forges restricts this target to specific CI forges by provider name (github, gitlab, gitea…
        outcomes: [<string>]   # Outcomes gates on the RUN outcome (success | failure | warning) — the notification dimension of…
        signals: [<string>]   # Signals gates on things the run FOUND, independent of its outcome — a run that succeeded can…
        schedules: [<string>]   # Schedules restricts this condition to runs fired by one of these schedules (ids from schedules:).…

kind: gitlab-component

publish:
  <id>:   # entry key = the unique id
    kind: gitlab-component   # Kind is the target type. Determines which fields are valid. · required
    spec_files: [<string>]   # SpecFiles lists component spec file paths (kind: gitlab-component).
    catalog: false   # Catalog enables GitLab Catalog registration (kind: gitlab-component).
    when:   # When specifies routing conditions for this target: a single condition-set, or a list of them (OR…
      - branches: [<string>]   # Branches lists branch filters. Each entry is a policy name or "re:<regex>". Empty = no branch…
        git_tags: [<string>]   # GitTags lists git tag filters. Each entry is a policy name or "re:<regex>". Empty = no tag…
        events: [<string>]   # Events lists CI event type filters. Supported: push, tag, release, schedule, manual, pull_request…
        forges: [<string>]   # Forges restricts this target to specific CI forges by provider name (github, gitlab, gitea…
        outcomes: [<string>]   # Outcomes gates on the RUN outcome (success | failure | warning) — the notification dimension of…
        signals: [<string>]   # Signals gates on things the run FOUND, independent of its outcome — a run that succeeded can…
        schedules: [<string>]   # Schedules restricts this condition to runs fired by one of these schedules (ids from schedules:).…

kind: release

publish:
  <id>:   # entry key = the unique id
    kind: release   # Kind is the target type. Determines which fields are valid. · required
    aliases: [<string>]   # Aliases are rolling git tag aliases (kind: release). e.g., ["{version}", "{major}.{minor}"…
    tag: <string>   # Tag is the immutable identity pattern for a release channel (kind: release). Distinct from Aliases…
    archives: <string>   # Archives references a binary-archive target ID (kind: release and generic-package).
    prerelease: false   # Prerelease marks the forge release as a pre-release (kind: release). DEPRECATED: prefer `type…
    signing_profile: <string>   # SigningProfile references a signing.profiles[].id — the trust profile this target signs under.…
    retention:   # Retention controls cleanup of old tags/releases. Structured only in v2 (no scalar shorthand).
      keep_last: <int>   # keep the N most recent tags per series (0/-1/unset = ∞) · required
      keep_daily: <int>   # keep one per day for the last N days · required
      keep_weekly: <int>   # keep one per week for the last N weeks · required
      keep_monthly: <int>   # keep one per month for the last N months · required
      keep_yearly: <int>   # keep one per year for the last N years · required
      keep_branches: <int>   # keep the N most-recent identity groups per template (bounds retired branches; 0/unset = ∞) · required
      max_age: <string>   # keep items newer than this ("14d", "72h"); additive like keep_* — alone it means "only the recent… · required
      identity: [<string>]   # extra identity vars beyond the {branch}/{env} defaults — partition tags into independent series · required
      protect: [<string>]   # tag patterns that are never deleted, an explicit override · required
      refs:   # scoped overrides: a different policy for items matching a pattern (first match wins; unset fields… · required
        - match: <string>   # pattern (same syntax as protect/templates) · required
          keep_last: <int>   # required
          keep_daily: <int>   # required
          keep_weekly: <int>   # required
          keep_monthly: <int>   # required
          keep_yearly: <int>   # required
          max_age: <string>   # required
    when:   # When specifies routing conditions for this target: a single condition-set, or a list of them (OR…
      - branches: [<string>]   # Branches lists branch filters. Each entry is a policy name or "re:<regex>". Empty = no branch…
        git_tags: [<string>]   # GitTags lists git tag filters. Each entry is a policy name or "re:<regex>". Empty = no tag…
        events: [<string>]   # Events lists CI event type filters. Supported: push, tag, release, schedule, manual, pull_request…
        forges: [<string>]   # Forges restricts this target to specific CI forges by provider name (github, gitlab, gitea…
        outcomes: [<string>]   # Outcomes gates on the RUN outcome (success | failure | warning) — the notification dimension of…
        signals: [<string>]   # Signals gates on things the run FOUND, independent of its outcome — a run that succeeded can…
        schedules: [<string>]   # Schedules restricts this condition to runs fired by one of these schedules (ids from schedules:).…

kind: binary-archive

publish:
  <id>:   # entry key = the unique id
    kind: binary-archive   # Kind is the target type. Determines which fields are valid. · required
    build: <string>   # Build references a BuildConfig.ID. Required for kind: registry.
    name: <string>   # Name is the archive filename template (kind: binary-archive). Supports: {id}, {version}, {os}…
    format: <string>   # Format is the archive format: "tar.gz", "zip", "auto", or "binary" (kind: binary-archive). "auto"…
    binary_name: <string>   # BinaryName overrides the binary name inside the archive (kind: binary-archive). Auto-detected from…
    include: [<string>]   # Include lists extra files to bundle into the archive (kind: binary-archive). e.g., ["README.md"…
    checksums: false   # Checksums generates a SHA256SUMS file alongside archives (kind: binary-archive).
    when:   # When specifies routing conditions for this target: a single condition-set, or a list of them (OR…
      - branches: [<string>]   # Branches lists branch filters. Each entry is a policy name or "re:<regex>". Empty = no branch…
        git_tags: [<string>]   # GitTags lists git tag filters. Each entry is a policy name or "re:<regex>". Empty = no tag…
        events: [<string>]   # Events lists CI event type filters. Supported: push, tag, release, schedule, manual, pull_request…
        forges: [<string>]   # Forges restricts this target to specific CI forges by provider name (github, gitlab, gitea…
        outcomes: [<string>]   # Outcomes gates on the RUN outcome (success | failure | warning) — the notification dimension of…
        signals: [<string>]   # Signals gates on things the run FOUND, independent of its outcome — a run that succeeded can…
        schedules: [<string>]   # Schedules restricts this condition to runs fired by one of these schedules (ids from schedules:).…

kind: generic-package

publish:
  <id>:   # entry key = the unique id
    kind: generic-package   # Kind is the target type. Determines which fields are valid. · required
    repo: <string>   # Repo references a repos[].id (kind: generic-package). The forge identity (provider, url, project…
    package: <string>   # Package is the generic package name (kind: generic-package). Defaults to the repo project's…
    version: <string>   # Version is the immutable package version pattern (kind: generic-package). Resolved against version…
    archives: <string>   # Archives references a binary-archive target ID (kind: release and generic-package).
    when:   # When specifies routing conditions for this target: a single condition-set, or a list of them (OR…
      - branches: [<string>]   # Branches lists branch filters. Each entry is a policy name or "re:<regex>". Empty = no branch…
        git_tags: [<string>]   # GitTags lists git tag filters. Each entry is a policy name or "re:<regex>". Empty = no tag…
        events: [<string>]   # Events lists CI event type filters. Supported: push, tag, release, schedule, manual, pull_request…
        forges: [<string>]   # Forges restricts this target to specific CI forges by provider name (github, gitlab, gitea…
        outcomes: [<string>]   # Outcomes gates on the RUN outcome (success | failure | warning) — the notification dimension of…
        signals: [<string>]   # Signals gates on things the run FOUND, independent of its outcome — a run that succeeded can…
        schedules: [<string>]   # Schedules restricts this condition to runs fired by one of these schedules (ids from schedules:).…

kind: pages

publish:
  <id>:   # entry key = the unique id
    kind: pages   # Kind is the target type. Determines which fields are valid. · required
    provider: <string>   # Provider is the vendor type for auth and API behavior. Registry: docker, ghcr, gitlab, jfrog… · one of: cloudflare, github
    build: <string>   # Build references a BuildConfig.ID. Required for kind: registry.
    dir: <string>   # Dir publishes a repo directory directly instead of a build's output tree (kind: pages). Exactly one…
    domain: [<string>]   # Domain is the custom domain(s) (kind: pages). Accepts a bare scalar or a list:
    project: <string>   # Project is the Cloudflare Pages project name (provider: cloudflare). Default: the target id.…
    base_path: <string>   # BasePath is the URL path the site is served under (kind: pages). Inferred per provider (Cloudflare…
    exclude: [<string>]   # Exclude drops matching paths from the publish workspace before deploy (kind: pages). Globs, applied…
    when:   # When specifies routing conditions for this target: a single condition-set, or a list of them (OR…
      - branches: [<string>]   # Branches lists branch filters. Each entry is a policy name or "re:<regex>". Empty = no branch…
        git_tags: [<string>]   # GitTags lists git tag filters. Each entry is a policy name or "re:<regex>". Empty = no tag…
        events: [<string>]   # Events lists CI event type filters. Supported: push, tag, release, schedule, manual, pull_request…
        forges: [<string>]   # Forges restricts this target to specific CI forges by provider name (github, gitlab, gitea…
        outcomes: [<string>]   # Outcomes gates on the RUN outcome (success | failure | warning) — the notification dimension of…
        signals: [<string>]   # Signals gates on things the run FOUND, independent of its outcome — a run that succeeded can…
        schedules: [<string>]   # Schedules restricts this condition to runs fired by one of these schedules (ids from schedules:).…