Skip to content

feat(helm): publish the chart to GHCR as a signed OCI artifact - #7563

Merged
waleedlatif1 merged 11 commits into
stagingfrom
feat/publish-helm-chart
Sep 7, 2026
Merged

feat(helm): publish the chart to GHCR as a signed OCI artifact#7563
waleedlatif1 merged 11 commits into
stagingfrom
feat/publish-helm-chart

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Publishes helm/sim to oci://ghcr.io/simstudioai/charts/sim on push to main. The chart has only ever existed inside the repo, which means self-hosters whose security process requires an officially published artifact can't use it — a chart pulled from a git checkout isn't something they can pin, mirror, or admit.
  • Signs every published version with Sigstore keyless signing and attaches a SLSA build-provenance attestation. Both are stored in the registry next to the chart, so they survive a mirror into an internal registry rather than only being retrievable from GitHub.
  • Pushes helm/artifacthub-repo.yml to the reserved artifacthub.io tag so the chart can be listed on Artifact Hub and the repo ownership claimed. Chart.yaml already carried the artifacthub.io/* annotations.
  • Switches the chart README and the Kubernetes docs page off the clone-and-install path, and adds verification instructions to both.

Design notes

  • The publish job lives in helm.yml, not a new publish-*.yml. It gates on needs: [chart, install], so nothing publishes unless the chart linted, passed its 108 unit tests, rendered clean under kubeconform, and actually installed on a kind cluster. A separate workflow would race those instead of waiting for them. This follows the release-job-in-the-chart-workflow pattern used by external-secrets and others.
  • Publishing is idempotent. A published chart version is immutable — whoever pinned it must keep resolving the same bytes — so a version already in the registry is skipped rather than overwritten. The existing PR gate already forces a Chart.yaml version bump on every chart change, so a skip means the commit changed something outside helm/sim/.
  • Signed by digest, never by tag. A tag is a mutable pointer, so signing one would attest to whatever it references later. The cosign verify step immediately after signing isn't ceremony: it fails the run if the signature can't be read back with the expected identity.
  • No GPG provenance. Signing is Sigstore-only, so there's no long-lived private key to hold or rotate. helm install --verify expects the GPG .prov format and won't apply; the docs say so and give the cosign verify equivalent instead.
  • The job is pinned to simstudioai/sim so a fork's main never attempts to publish into a namespace it doesn't own.
  • Chart bumped 1.9.0 → 1.9.1 for the README change (the chart's own PR gate requires it). No template or values changes.

Operational note before this can serve traffic

The first push creates a private GHCR package. Someone with org admin needs to flip simstudioai/charts/sim to public once, or helm install will 401 for everyone. Nothing in the workflow can do this itself.

Optional follow-up: register the repo at artifacthub.io and fill in repositoryID in helm/artifacthub-repo.yml for the Verified Publisher badge. The listing works without it.

Type of Change

  • New feature (non-breaking change which adds functionality)

Testing

Validated locally against the chart:

  • helm lint clean; both CI value sets (default-values.yaml, full-values.yaml) render
  • helm-unittest: 108 tests / 16 suites pass with the version bump
  • helm package produces a clean .tgz — confirmed artifacthub-repo.yml, examples/, tests/, and .claude/ are all excluded
  • Confirmed the awk extraction of chart name/version matches exactly once each against real helm show chart output (the maintainers entry is indented, so it doesn't collide)
  • Confirmed Helm reads --values over HTTPS, which is what lets the cloud-specific docs work without a checkout now that the example files aren't in the packaged chart
  • check:cron-parity and images:check (the existing helm CI gates) pass
  • bun run lint, check:audits (45 audits), docs-manifest:check, and the block-registry audit all pass

The publish path itself can only be exercised on a main push — it's gated on push + refs/heads/main, so this PR runs everything except the publish job.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

The chart has only ever existed inside the repo, so self-hosters whose
security process requires an officially published artifact could not use
it at all — a chart pulled from a git checkout is not something they can
pin, mirror, or admit.

Publishes helm/sim to oci://ghcr.io/simstudioai/charts/sim on push to
main, signed with Sigstore keyless signing and carrying a SLSA build
provenance attestation, both stored in the registry so they survive a
mirror into an internal registry.

The publish job lives in helm.yml rather than its own publish-*.yml so it
can gate on the existing jobs: nothing ships unless the chart linted,
unit-tested, rendered clean under kubeconform, and installed on kind.
Publishing is idempotent — a version already in the registry is skipped
rather than overwritten, since a published chart version is immutable.

Also pushes helm/artifacthub-repo.yml to the reserved artifacthub.io tag
for the Artifact Hub listing, and switches the chart README and the
Kubernetes docs page off the clone-and-install path.
@vercel

vercel Bot commented Sep 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Sep 7, 2026 6:15pm UTC

Request Review

@greptile-apps

greptile-apps Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Publishes the Sim Helm chart through OCI and classic Helm repositories after existing chart validation succeeds.

  • Packages, signs, verifies, and attests new OCI chart versions in GHCR.
  • Publishes Artifact Hub repository metadata and supports a chart-releaser-backed HTTP repository.
  • Updates chart and image versions and migrates self-hosting documentation to pinned published artifacts.
  • No changes were made since the previous review, and all previous findings are resolved.

Confidence Score: 5/5

The PR appears safe to merge because no unresolved or newly introduced actionable failures remain.

All previous findings are resolved, no code changed after the previous review, and the full-PR rules check found no concrete violations.

Important Files Changed

Filename Overview
.github/workflows/helm.yml Adds gated OCI signing, provenance, Artifact Hub metadata publishing, and classic Helm repository release jobs.
helm/artifacthub-repo.yml Adds repository-level Artifact Hub ownership metadata outside the packaged chart.
helm/sim/Chart.yaml Bumps the chart and application versions and adds the Artifact Hub category.
helm/sim/README.md Documents pinned OCI and HTTP installation, mirroring, and Sigstore verification.
apps/docs/content/docs/platform/self-hosting/kubernetes.mdx Replaces checkout-based deployment guidance with pinned published-chart installation and verification.
helm/sim/images.yaml Aligns the mirrored application image inventory with appVersion v0.8.24.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  Push[Push to main] --> Chart[Lint, test, and validate chart]
  Push --> Install[Install and test on kind]
  Chart --> OCI[OCI publish job]
  Install --> OCI
  Chart --> HTTP[HTTP repository publish job]
  Install --> HTTP
  OCI --> Package[Package chart]
  Package --> Version{Version already published?}
  Version -->|No| GHCR[Push chart to GHCR]
  GHCR --> Sign[Sign and verify by digest]
  Sign --> Attest[Attach build provenance]
  Version -->|Yes| Skip[Preserve immutable version]
  Attest --> Metadata[Publish Artifact Hub metadata]
  Skip --> Metadata
  HTTP --> Pages{gh-pages exists?}
  Pages -->|Yes| Release[Publish chart release and index]
  Pages -->|No| Warn[Skip with warning]
Loading

Reviews (12): Last reviewed commit: "fix(helm): fail the appVersion check onl..." | Re-trigger Greptile

Comment thread .github/workflows/helm.yml Outdated
Comment thread apps/docs/content/docs/platform/self-hosting/kubernetes.mdx Outdated
Comment thread helm/sim/README.md
…nstalls

The publish guard treated every non-zero `helm show chart` exit as proof the
version was unpublished, so a transient 5xx, an expired token, or a DNS blip
would have enabled a push that moves an already-published version tag. Runs
where the version already exists are routine — the path filter also fires on
package.json and workflow edits — so this was reachable.

Now only an explicit `: not found` counts as absent; anything else stops the
job. Verified against the pinned Helm v3.16.4 that an absent version and an
absent repository both report `: not found`, so a first publish still
proceeds, while denied/unauthorized/dial-tcp failures abort instead.

Also pins the two documented install paths that were still reproducible only
by accident: the cloud-specific command paired a pinned chart with values
fetched from the moving main branch, and the README quick start omitted
--version while the same README warns against exactly that.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

…sign v3

Ran the two standard workflow linters over helm.yml. actionlint reported one
shellcheck style issue; zizmor reported 22 findings, including two High.

Both High findings predate this PR: the version gate expanded
github.base_ref straight into a run block. The three Medium findings were
checkouts leaving the token in .git/config. Fixed all of them here rather
than leaving known findings in a file this PR already rewrites -- the gate
only reads history and fetches a public branch, so it never needed the
credential.

The 16 informational findings were mine: every ${{ }} in a run block is
template expansion before the shell sees it, so the documented fix is to
pass values through env and reference them as shell variables. Done for the
whole publish job, which also removes the shellcheck nit.

zizmor now reports zero findings on the file in pedantic mode, with no
suppressions, and actionlint is clean.

Separately, cosign v3 writes the Sigstore protobuf bundle format by default
and cosign v2 cannot read it, so the verification instructions now state the
required version. The in-workflow verify uses the same binary it signs with,
so it could never have caught this.

Re-verified the existence guard's four branches after the env refactor, and
confirmed it also fails closed when the registry login has not happened.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

…atches

Dry-ran the publish sequence against a local OCI registry, which surfaced one
divergence from Artifact Hub's documented command: passing a path-qualified
argument to oras records the layer's title annotation as
`helm/artifacthub-repo.yml` rather than the bare `artifacthub-repo.yml` the
docs produce. Artifact Hub selects the layer by media type, so this was
probably harmless, but there is no reason to diverge from the documented
form. Running the step from helm/ reproduces it exactly.

Everything else in the sequence behaved as intended against a real registry:
helm push derives charts/sim:1.9.1 from the chart name and version, the
digest grep extracts it, the metadata lands with both documented media types
under the artifacthub.io tag, the chart and metadata tags coexist without
disturbing each other, and helm pull round-trips to an identical sha256.

Also confirmed the overwrite hazard behind the fail-closed guard is real
rather than theoretical: re-pushing modified content under an existing
version moved the tag, so a pinned consumer would have silently received
different bytes.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

The chart carried `category: developer-tools`, which is a different
convention entirely -- Artifact Hub reads `artifacthub.io/category` and
accepts only one value from a fixed list that has no developer-tools entry.
The listing this PR sets up would therefore have published uncategorised.

Adds `artifacthub.io/category: ai-machine-learning`, the closest fit in that
list, and keeps the bare annotation for whatever else consumes it. Bumps to
1.9.2 per the chart's own gate and moves the documented versions with it.

Deliberately not adding `artifacthub.io/images`: Artifact Hub already
extracts images from a dry-run render, and a hand-listed copy would be a
second source of truth next to the generated images.yaml.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

I claimed earlier that OCI-only was defensible in 2026. That was wrong, and
checking rather than reasoning settled it: Bitnami, cert-manager,
ingress-nginx, prometheus-community, Grafana, Argo and external-secrets all
still serve a live index.yaml. Dual-publish is the actual convention, and
external-secrets -- the workflow this one is modelled on -- does both.

Adds a chart-releaser job that maintains index.yaml on a pages branch and
attaches each packaged chart to a GitHub release. It is a separate job from
the OCI publish because it needs contents: write to cut that release, and
that permission has no business sitting in the job that holds the signing
identity.

Three details worth stating:

- mark_as_latest is false. A chart release must never take the "Latest"
  badge from the application release it packages.
- Releases are named helm-chart-<version> so they stay distinguishable from
  the vX.Y.Z app releases in the same list.
- skip_existing mirrors the OCI job: re-running on a published version is a
  no-op rather than an overwrite.

The job no-ops with a warning until a gh-pages branch exists, because
creating that branch and enabling Pages are one-time manual steps no
workflow can perform for itself, and main's CI must not fail on their
absence.

No documentation yet. index.yaml is only reachable once the branch, Pages,
and DNS are in place, and pointing users at a hostname that does not resolve
is the exact failure this whole PR exists to remove.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

Held these back until the hostname actually resolved, because shipping an
install command that points at a dead domain is the exact failure this work
exists to remove. It resolves now: DNS is in place, GitHub Pages serves the
branch, and Let's Encrypt has issued the certificate.

Verified end to end rather than assumed:

  helm repo add sim https://charts.sim.ai
  helm repo update            # Successfully got an update

Presents OCI as the preferred path in both places and the repository as the
compatibility path, and says plainly that the signature and provenance are
attached to the OCI artifact only -- `helm repo add` has no equivalent, and
leaving that implicit would let someone believe they had verified something
they had not.

Bumps to 1.9.3 per the chart's own gate, since this touches helm/sim/.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

The Terraform page still told readers the chart "is not published to a Helm
repository or an OCI registry, so there is no repository to point at", and
had them vendor the repo as a submodule or git clone. That is now false, and
it is the same claim that makes a self-hoster conclude Sim is not packaged
for them, so it is the most important line in the docs to correct.

It now points repository at the OCI registry with a pinned version, notes
the classic repository as the alternative for tooling that does not speak
OCI, and replaces the "because the chart is local, version does nothing"
paragraph -- pinning the chart version is now both possible and the thing
that stops an unplanned apply pulling new migrations. Syntax checked against
the provider docs; the page's existing set_sensitive list form is already v3.

Also switches the two upgrade commands and the env render check off ./helm/sim,
which no longer exists for anyone following the install path.

Deliberately unchanged, because these are not install references:

- The chart README links on the Kubernetes page. The README lives in the
  repo; charts.sim.ai serves index.yaml, not documentation.
- The cosign --certificate-identity-regexp. That is a Sigstore OIDC identity
  that happens to look like a URL, and rewriting it would break verification.
- The releases page and issue tracker links, which point where they should.
- The git clone in the quick start, which is the Docker Compose path and
  genuinely does need the repo.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

Swept the remaining checkout-era references and audited the Helm docs by
checking every values path they mention against values.yaml rather than
reading them.

That found a real error: the chart has no `copilot.env`. The map is
`copilot.server.env`, so anyone following the env-validation note would have
set a key that silently does nothing. Fixed.

It also flagged `app.envDefaults.FREE_TABLES_LIMIT` and
`app.secrets.existingSecret.keys`, which are NOT bugs -- both appear only in
upgrade notes describing keys older releases shipped and this one removed.
Left exactly as they are; "fixing" them would delete the history an upgrader
needs.

The chart README was the bigger problem. It led with registry install after
the earlier commits, then used `./helm/sim` in eleven subsequent commands --
upgrade, dry-run, examples, parameters, secrets, troubleshooting. Someone
following the documented install had no such path. They now use the OCI
reference with a pinned version, with one note establishing that convention
so the checkout form does not have to be repeated everywhere.

Two related corrections while in there: the example values files are not part
of the packaged chart, so the examples section now fetches one at a release
tag instead of assuming it locally; and values.schema.json cannot be `cat`ed
without a checkout, so it links to the repo or `helm pull --untar`.

Left pointing at GitHub on purpose: the chart README deep links (that is
where the README lives, and an extra hop through the repository root would
be worse), the releases page, the issue tracker, the cosign identity regexp,
and the Docker Compose quick start's git clone.

Bumps to 1.9.4 per the chart's own gate.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

appVersion sat at v0.8.18 while the app shipped through v0.8.24 -- six
releases. Because the image tags default to Chart.AppVersion, a default
`helm install` deployed the older Sim, and regenerating images.yaml here
moved eleven lines, so the mirror inventory an air-gapped operator works
from named the wrong images too.

Publishing is what makes this serious. Cloning main got you whatever was
there; a published chart version is immutable, so every stale appVersion
would be frozen and installable forever.

It was bumped by hand and nothing checked it, which is why it drifted. The
publish job now refuses to publish when appVersion does not match the latest
GitHub release, comparing against the release API rather than a hardcoded
value so the check cannot go stale itself. Confirmed it fires on exactly the
drift that existed (v0.8.18 vs v0.8.24) and passes now that it is fixed.

Bumps appVersion to v0.8.24, regenerates images.yaml, and takes the chart to
1.9.5.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

Comment thread .github/workflows/helm.yml
…smatch

The check compared appVersion to /releases/latest for equality, which races
the release it is meant to protect. Version tags are cut by the main-branch
merge commit that releases them, so on that commit appVersion legitimately
names a release that does not exist yet while ci.yml is still building it.
Equality would have rejected the bump and blocked the very publish it was
for. helm/sim/ci/kind-values.yaml already documents this circularity, and
records it as the reason appVersion went unbumped from chart 1.2.0 to 1.6.3.

Lagging is the failure; being ahead is a normal transient. The comparison is
`sort -V`, so it orders versions rather than strings -- v0.9.9 against
v0.10.0 is exactly where a string compare silently inverts.

Verified all five cases: behind fails, equal passes, ahead passes with a
notice, and both multi-digit orderings resolve correctly.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

Comment thread helm/artifacthub-repo.yml
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
waleedlatif1 merged commit 776f148 into staging Sep 7, 2026
35 checks passed
@waleedlatif1
waleedlatif1 deleted the feat/publish-helm-chart branch September 7, 2026 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant