Set up trusted publishing#

Trusted publishing is the recommended way to publish new releases for projects managed in GitHub or GitLab, because it removes the need to store a long-lived secret token in CI. It works by instead declaring to Sysand Index that you trust automation in GitHub Actions or GitLab CI to publish new releases of a project.

Instead of a reusable token that can leak and publish until revoked, each publish job authenticates with a fresh short-lived provider token, and the release is linked to the specific repository, workflow, environment, and protected Git ref that published it. For the full rationale and token exchange behavior, see Trusted publishing.

The same setup supports hosted github.com and gitlab.com, GitHub Enterprise Server, and self-managed GitLab. For Enterprise or self-managed providers, enter the provider hostname when adding the trusted publisher.

Before you start#

If you are publishing a project for the first time, first publish it with a quickly expiring account API token by following Publishing your first project.

You must have effective project Owner access on Sysand Index: only project owners see the trusted publishing tab used below. For organization projects, organization Owner access by itself is not enough; see Roles and permissions.

If you don’t version control your Sysand project yet, first set it up under GitHub or GitLab. Details on doing this are not part of this guide.

You need enough access to configure the CI environment and protect the branch or tag that runs publishing. On GitHub, this means repository admin access. On GitLab, Maintainer access is needed to protect branches or tags.

Add the trusted publisher#

Go to Projects, then click Manage on the project you want to configure, then click the trusted publishing tab.

Under the Add a new publisher header, choose the GitHub or GitLab tab, fill in the provider fields, and submit the form. For exact field rules and matching behavior, see Trusted publishing.

Fill in:

  • GitHub domain: leave blank for github.com; otherwise enter the hostname of your GitHub Enterprise Server instance, such as github.example.com

  • Repository full name: the GitHub owner and repository in owner/repository form, such as my-org/my-project

  • Repository ID: leave blank for a public repository

  • Workflow filename: the file name under .github/workflows/, such as release.yaml

  • Environment: the GitHub Actions environment name, such as sysand

Press Add GitHub publisher.

Fill in:

  • GitLab domain: leave blank for gitlab.com; otherwise enter the hostname of your GitLab instance, such as gitlab.example.com

  • Project path: the full GitLab namespace and project path, including any subgroups, such as my-org/my-subgroup/my-project

  • Project ID: leave blank for a public project

  • Pipeline file: the CI configuration file path; leave blank to use .gitlab-ci.yml

  • Environment: the GitLab environment name, such as sysand

Press Add GitLab publisher.

For a private repository or project, you need to enter the permanent provider ID yourself. See Private repository and project IDs for how to find it.

Create the CI environment#

GitHub and GitLab have a concept of CI environments, and we need to create one in the repository or project. Using environments helps constrain what parts of the CI automation should be allowed to publish to Sysand Index.

Go to your GitHub project and navigate through Settings -> Environments, and then click the New environment button. You should now be at a URL looking like https://github.com/my-org/my-project/settings/environments/new.

Fill in the name sysand and press Configure environment.

We recommend also configuring the environment to require manual approval.

Go to your GitLab project and navigate through Operate -> Environments, then click Create an environment.

Fill in the name sysand and press Save.

On GitLab Premium or Ultimate, we recommend also configuring the environment as a protected environment with a manual approval step. Navigate through Settings -> CI/CD, expand Protected environments, select the sysand environment, configure who can deploy and who can approve deployments, and press Protect.

Protect the release ref#

Sysand Index only accepts trusted publishing from protected branches or protected tags. The examples in this guide publish when a tag such as v1.2.3 is pushed, so protect the tag pattern v* before testing the automation.

If your release workflow publishes from a branch instead of tags, protect that branch, such as main, instead.

In GitHub, go to the repository, then open Settings -> Rules -> Rulesets. Create a tag ruleset, set enforcement to active, and target tags matching v*.

For teams publishing from a branch, create a branch ruleset for the release branch instead.

In GitLab, go to the project, then open Settings -> Repository. Expand Protected tags, add the tag pattern v*, and allow only Maintainers to create matching tags.

For teams publishing from a branch, use Protected branches on the same page and protect the release branch instead.

Add the CI configuration#

After adding the trusted publisher, copy the matching CI configuration example below into the repository that you registered as the trusted publisher.

The examples reference https://sysand.com, but you should update SYSAND_INDEX_URL to for example https://test.sysand.com if that is where you added the trusted publisher.

Each example grants the publish job access to the CI provider’s OIDC token. With sysand 0.1.5 or later, sysand publish uses that token to perform trusted publishing automatically. In the default auto mode, supported CI trusted publishing is preferred over configured bearer-token credentials. For a monorepo that publishes more than one Sysand project, run a separate sysand publish command for each project.

Copy this example to .github/workflows/release.yaml.

# This is a GitHub workflow, defining a set of jobs each with a set of steps.
#
# The workflow will run when a git tag like v1.2.3 is pushed, and publish a
# release to the chosen sysand index using trusted publishing.
#
# trusted publishing reference:  __SYSAND_DOCS_ROOT__/index/how-to/set-up-trusted-publishing/
# workflow config reference:     https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
# installation script reference: __SYSAND_DOCS_ROOT__/client/getting-started/installation/#installation-script
#
name: Release

on:
  push:
    tags: ["v*"]

env:
  SYSAND_INDEX_URL: https://sysand.com

jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v6

      - name: Install sysand
        run: |
          curl -fsSL https://sysand.com/install.sh | SYSAND_VERSION=latest sh

      - name: Check version in git tag matches the project's declared version
        run: |
          PROJECT_VERSION="$(sysand info version)"
          TAG_VERSION="${GITHUB_REF_NAME#v}"
          if [ "$PROJECT_VERSION" != "$TAG_VERSION" ]; then
            echo "Project version ${PROJECT_VERSION} does not match tag ${GITHUB_REF_NAME}"
            exit 1
          fi

  publish:
    runs-on: ubuntu-latest
    needs: [test]
    environment: sysand
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v6

      - name: Install sysand
        run: |
          curl -fsSL https://sysand.com/install.sh | SYSAND_VERSION=latest sh

      - name: Build and publish a project to the index
        run: |
          sysand build
          sysand publish --index "${SYSAND_INDEX_URL}"

Copy this example to .gitlab-ci.yml.

# This is a GitLab pipeline configuration, defining a set of jobs grouped into
# stages.
#
# The pipeline will run when a git tag like v1.2.3 is pushed, and publish a
# release to the chosen sysand index using trusted publishing.
#
# The image used is based on Alpine's official image, with sysand, curl, and
# ca-certificates installed.
#
# trusted publishing reference: __SYSAND_DOCS_ROOT__/index/how-to/set-up-trusted-publishing/
# pipeline config reference:    https://docs.gitlab.com/ci/yaml/
# sysand image reference:       https://github.com/sensmetry/sysand/blob/main/Dockerfile
#
workflow:
  rules:
    - if: $CI_COMMIT_TAG

variables:
  SYSAND_INDEX_URL: https://sysand.com

stages:
  - test
  - publish

test:
  stage: test
  image: ghcr.io/sensmetry/sysand:latest-alpine
  script:
    # check version in git tag matches the project's declared version
    - |
      PROJECT_VERSION="$(sysand info version)"
      TAG_VERSION="${CI_COMMIT_TAG#v}"
      if [ "$PROJECT_VERSION" != "$TAG_VERSION" ]; then
        echo "Project version ${PROJECT_VERSION} does not match tag ${CI_COMMIT_TAG}"
        exit 1
      fi

publish:
  stage: publish
  environment:
    name: sysand
    url: https://sysand.com
  id_tokens:
    GITLAB_OIDC_TOKEN:
      aud: sysand
  image: ghcr.io/sensmetry/sysand:latest-alpine
  script:
    # build and publish a project to the index
    - |
      sysand build
      sysand publish --index "${SYSAND_INDEX_URL}"

Test the automation#

Push a Git tag#

To test the publishing workflow, first update the Sysand project version to the version you want to publish, commit it, and push a tag. While this can be done in a few ways, here is one.

$ sysand info version --set 1.2.3

Commit that version change and push it to the registered repository:

$ git add .project.json
$ git commit -m "Release 1.2.3"
$ git push

Then create and push a matching tag:

$ git tag v1.2.3
$ git push origin v1.2.3

This should now trigger the configured CI automation to run.

Inspect the triggered CI automation#

After pushing the tag, inspect the CI run for the provider you configured.

Go to your GitHub project and click the Actions tab.

Open the Release workflow run for the tag you pushed, such as v1.2.3. If the sysand environment requires approval, approve the deployment before the publish job starts.

Open the test job and check that it verifies the tag version against the declared Sysand project version. Then open the publish job and check that the steps install sysand, build the project, and publish it.

Go to your GitLab project and navigate through Build -> Pipelines.

Open the pipeline for the tag you pushed, such as v1.2.3. If the sysand environment requires deployment approval, approve the deployment before the publish job starts.

Open the test job and check that it verifies the tag version against the declared Sysand project version. Then open the publish job and check that it builds and publishes the project.

When the publish step succeeds, go to Projects, then click the project.

Check that the new release appears and that the rendered README, changelog, licenses, and usages look correct.

If trusted publishing authentication fails#

Use the error message from the CI job to narrow the problem:

  • If the message says the branch or tag was not protected, protect the tag pattern or branch that triggers publishing and rerun the CI job from that ref.

  • If the message says no matching trusted publisher was found, compare the repository or project ID, workflow or pipeline file, environment, and provider domain with the trusted publisher you added.

  • If the message says the token has expired, make sure the workflow requests the provider token during the publish job.

If trusted publishing authentication succeeds but upload fails, troubleshoot it like any other publish failure. The short-lived token can only publish to the matched project, can be used for only one successful upload, and each version can be published only once. For the full exchange failure table, see Trusted publishing. For archive validation issues, see KPAR archive validation.