Goalkeeper Docs

Releases

Versioning, published artifacts, and operator verification.

Goalkeeper has one product version for the repository. Internal packages remain private implementation units and are not versioned or published independently.

Versioning policy

Goalkeeper follows Semantic Versioning with vMAJOR.MINOR.PATCH Git tags.

  • Before 1.0.0, incompatible and backwards-compatible features increment MINOR; fixes increment PATCH.
  • After 1.0.0, incompatible changes increment MAJOR, backwards-compatible functionality increments MINOR, and fixes increment PATCH.
  • A version covers the REST API, MCP server, database migrations, browser app, and all other source in this repository as one tested release.

The root package.json is the source version. Goalkeeper is distributed under the Apache License 2.0.

Release automation

Three workflows divide preparation, validation, and publication:

TriggerWorkflowResponsibility
Push to mainrelease.ymlOpen or update the Release Please pull request. After that pull request merges, create the tag and draft GitHub Release, then call the publication workflow.
Pull request or push to mainci.ymlTypecheck, test with PostgreSQL, build, verify generated files, build the static browser distribution, and build all three container images without publishing them.
Release workflow call or manual dispatchpublish-artifacts.ymlValidate the immutable tag, rebuild and attest release artifacts, publish images and files, then make the draft GitHub Release public.

The release workflow is the only automated tag creator. Release tags are immutable and must never be created, moved, or replaced manually.

Repository configuration

Configure a fine-grained RELEASE_PLEASE_TOKEN Actions secret scoped only to this repository with these repository permissions:

  • Contents: read and write;
  • Pull requests: read and write.

Release Please uses this token instead of the repository GITHUB_TOKEN so creating or updating its pull request triggers the normal pull-request checks. Artifact publication uses the short-lived repository GITHUB_TOKEN with the job-specific package, content, attestation, and OIDC permissions declared in the workflows.

Before a public release, confirm that:

  • branch protection requires the release pull request's CI checks;
  • the goalkeeper-api, goalkeeper-mcp, and goalkeeper-web GHCR packages inherit the intended visibility from this repository; and
  • GitHub Actions is permitted to publish packages and provenance attestations.

Preparing a release

Release Please maintains one release pull request from Conventional Commit messages on main:

  • fix: proposes a patch release;
  • feat: proposes a minor release;
  • a BREAKING CHANGE: footer or ! proposes an incompatible release.

Before 1.0.0, the configured bump-minor-pre-major policy keeps incompatible changes on the pre-GA minor line. Goalkeeper's bootstrap release was pinned to 0.1.0 with a one-time release-as override. Do not leave release-as configured after a forced release version; doing so requests that same version on later release runs.

Each push to main reruns release.yml. Release Please updates the same pull request, including:

  • the version in package.json;
  • the version recorded in .release-please-manifest.json; and
  • the generated CHANGELOG.md entry.

Review the proposed version and changelog, then require all CI jobs to pass. The four artifact-oriented checks on the pull request are:

  • Build web distribution;
  • Build goalkeeper-api image;
  • Build goalkeeper-mcp image;
  • Build goalkeeper-web image.

These checks prove that the artifacts can be constructed from the release commit. They do not push images, create attestations, or modify a GitHub Release.

Publishing a release

Merging the release pull request causes the next release.yml run to create the matching vMAJOR.MINOR.PATCH tag and a draft GitHub Release. It then calls publish-artifacts.yml with that tag. Publication proceeds in this order:

  1. check out the tag and verify that it exactly matches the version in package.json;
  2. verify that the tag resolves to the checked-out commit;
  3. regenerate contracts and run the full typecheck, test, build, and PostgreSQL migration suite against the tag;
  4. create, verify, checksum, attest, and attach the static browser distribution;
  5. build and push the API, MCP, and web images for linux/amd64 and linux/arm64, then attach registry provenance attestations; and
  6. change the GitHub Release from draft to published only after every preceding job succeeds.

Failure and retry behavior

If validation or artifact publication fails, the GitHub Release remains a draft. Fix the workflow or transient external failure without moving the tag, then rerun publication for the existing tag:

version=0.1.0
gh workflow run publish-artifacts.yml \
  --repo poggiolabs/goalkeeper \
  --ref "v$version" \
  -f ref="v$version"

The workflow dispatch ref and ref input must name the same tag. This ensures that checkout, version validation, provenance identity, and published artifact labels all resolve to one release commit. Uploads use replacement semantics for the draft release assets, and image publication is repeatable for the same immutable source tag.

Published artifacts

Each release publishes these multi-platform images:

  • ghcr.io/poggiolabs/goalkeeper-api:<version>
  • ghcr.io/poggiolabs/goalkeeper-mcp:<version>
  • ghcr.io/poggiolabs/goalkeeper-web:<version>

Stable releases also update latest; prereleases do not. Deployments should pin an exact version or, preferably, the published digest. Every image uses digest-pinned base images and includes OCI source, version, and revision labels plus GitHub/Sigstore build-provenance attestations.

The release also attaches a static browser distribution for CDN or object storage deployments:

  • goalkeeper-web-dist-<version>.tar.gz
  • goalkeeper-web-dist-<version>.tar.gz.sha256
  • goalkeeper-web-dist-<version>.intoto.jsonl

The archive root contains goalkeeper-manifest.json with the product version and full Git revision. It also contains an empty runtime-config.js placeholder. Set window.__GOALKEEPER_CONFIG__ before serving the archive, or use the web image's GOALKEEPER_API_URL and GOALKEEPER_DOCS_URL environment variables. An empty API URL selects the browser origin for same-origin reverse proxy deployments.

Download and verify all release assets before extraction:

version=0.1.0
gh release download "v$version" \
  --repo poggiolabs/goalkeeper \
  --pattern "goalkeeper-web-dist-$version*"
source_digest="$(gh api "repos/poggiolabs/goalkeeper/commits/v$version" --jq .sha)"
sha256sum --check "goalkeeper-web-dist-$version.tar.gz.sha256"
gh attestation verify "goalkeeper-web-dist-$version.tar.gz" \
  --repo poggiolabs/goalkeeper \
  --bundle "goalkeeper-web-dist-$version.intoto.jsonl" \
  --source-digest "$source_digest" \
  --signer-workflow poggiolabs/goalkeeper/.github/workflows/publish-artifacts.yml

The MCP image requires a deployment-accurate PUBLIC_MCP_URL and corresponding host/origin policy. The API and MCP images share the same PostgreSQL schema and run migrations under a database advisory lock, so they can start concurrently.

The API image requires production email delivery:

AUTH_EMAIL_DELIVERY=smtp
AUTH_EMAIL_FROM='Goalkeeper <noreply@example.com>'
AUTH_SMTP_URL='smtps://user:password@smtp.example.com:465'

Use smtp:// for STARTTLS (port 587 by default) or smtps:// for implicit TLS (port 465 by default). AUTH_EMAIL_DELIVERY=log is restricted to non-production processes because authentication messages contain live bearer links.

The documentation site is not an operator runtime and is therefore not published as a release image.