Skip to content

Release pipeline: tag-triggered goreleaser to GitHub Releases + GHCR#4

Merged
cjimti merged 1 commit into
mainfrom
add-release-workflow
May 10, 2026
Merged

Release pipeline: tag-triggered goreleaser to GitHub Releases + GHCR#4
cjimti merged 1 commit into
mainfrom
add-release-workflow

Conversation

@cjimti
Copy link
Copy Markdown
Contributor

@cjimti cjimti commented May 10, 2026

Summary

Closes the M5-deferred gap that left v* tag pushes producing nothing on GitHub. Mirrors the sister project's (mcp-test) release setup verbatim, adapted for api-test naming and the HTTP-fixture description.

You pushed v1.0.0, no Action ran, and there was no release.yml to run — that's what this fixes.

What's in here

.github/workflows/release.yml

Trigger: push: tags: v*. Single release job on ubuntu-24.04, 30-min timeout. Sets up Go 1.26.3 + Node 22 + pnpm 10 (so the embedded SPA is fresh in the released binary), QEMU + Buildx for multi-arch image builds, logs into GHCR with the workflow's GITHUB_TOKEN, then runs goreleaser.

Hardening:

  • permissions: {} at workflow level. Job grants only contents: write (release upload), packages: write (GHCR push), id-token: write (cosign keyless when wired).
  • All third-party actions pinned to commit SHAs (checkout, setup-go, setup-node, pnpm/action-setup, docker/setup-qemu-action, docker/setup-buildx-action, docker/login-action, goreleaser-action).
  • fetch-depth: 0 so goreleaser's changelog walks full history.

.goreleaser.yml (v2)

Five-platform binary matrix: darwin/{amd64,arm64}, linux/{amd64,arm64}, windows/amd64. windows/arm64 ignored (no current consumer). Build flags: CGO_ENABLED=0 -trimpath -a -s -w. Version metadata stamped through -ldflags -X against pkg/build.{Version,Commit,Date}, matching the Makefile's existing convention. mod_timestamp: '{{ .CommitTimestamp }}' for reproducible builds.

Archives:

  • tar.gz on Unix, zip on Windows
  • Bundle LICENSE + README.md
  • Name template {{.ProjectName}}_{{.Version}}_{{.Os}}_{{.Arch}}
  • checksums.txt (sha256)

Container image (dockers_v2:):

  • ghcr.io/plexara/api-test
  • Tags: latest, {{ .Tag }}, v{{ .Major }}
  • Multi-arch: linux/amd64, linux/arm64
  • OCI labels: created/title/revision/version/source/description/licenses/vendor/url
  • extra_files: [LICENSE] so the OCI license file is in the build context

Changelog: use: github (uses PR titles), grouped by regex into Features (^feat) / Bug Fixes (^fix) / Security (^security) / Others. Excludes ^docs:, ^test:, ^chore:, ^build:, merges.

Release notes: prerelease auto-detect; mode: append so re-runs don't replace the body. Footer includes the docker pull command, the curl one-liner for binaries with auto-detected os/arch, and a docs link.

SPA before-hook: corepack enable && pnpm install --frozen-lockfile && pnpm build against ui/, then copies ui/dist into internal/ui/dist so the //go:embed directive in internal/ui ships the up-to-date portal in every release artifact. Falls back to npm install && npm run build. Skipped silently if ui/package.json is missing.

Dockerfile — rewritten

Previous Dockerfile did its own in-stage build (WORKDIR /src; COPY . .; RUN go build). That conflicted with the existing make docker target (d31fc12 Makefile) which already pre-stages a binary into linux/amd64/api-test before invoking buildx — exactly the layout goreleaser uses. The Dockerfile was the odd one out.

New Dockerfile (mcp-test pattern):

  • FROM alpine:3.23 AS certs → ca-certificates layer
  • FROM scratch runtime
  • COPY linux/${TARGETARCH}/api-test /usr/local/bin/api-test — consumes goreleaser- or Make-staged binary
  • USER 1000:1000 (scratch has no /etc/passwd)
  • HEALTHCHECK preserved — CMD ["/usr/local/bin/api-test", "--healthcheck"] exists in cmd/api-test/main.go:32 (verified before keeping)
  • ENTRYPOINT/CMD the same shape; default config path /app/configs/api-test.yaml for operators to mount

Dockerfile.dev (used by docker-compose.dev.yml) is untouched.

.dockerignore

mcp-test parity. Excludes bin/, dist/, linux/, site/, coverage, all node_modules, ui/dist, internal/ui/dist, docs/, mkdocs.yml, repo metadata, .env* (with .env.example allowlisted), IDE/OS noise, and the local screenshot tooling's node_modules.

.gitignore

Adds /dist/ (goreleaser output) and /linux/ (make docker scratch dir). Both were untracked-but-uncommitted before; now they're explicitly ignored.

Verification

  • goreleaser check → 1 config validated, no warnings.
  • goreleaser release --snapshot --clean --skip=publish,docker,sign,before → all 5 cross-platform binaries built, 5 archives produced (api-test_1.0.1-next_*.tar.gz + _windows_amd64.zip), checksums.txt written. 18s total.
  • before skipped locally because corepack isn't installed on this Mac. CI installs pnpm via pnpm/action-setup, so the SPA hook runs there.
  • docker skipped locally because the snapshot path doesn't exercise GHCR push; the Dockerfile syntax is otherwise straightforward.

Test plan

Re-tagging after this merges is what actually exercises the workflow. Suggested sequence:

  • Merge this PR.
  • Re-create the tag at the new merge SHA: git tag v1.0.0 && git push origin v1.0.0.
  • Watch the Release workflow run in Actions; expect ~5–10 min total (Go build + cross-compile + multi-arch image build).
  • Confirm the new GitHub Release at https://github.com/plexara/api-test/releases/tag/v1.0.0 has 5 archives + checksums.txt.
  • Confirm ghcr.io/plexara/api-test:v1.0.0, :v1, :latest are pushed: docker pull ghcr.io/plexara/api-test:v1.0.0.
  • Spot-check docker run --rm ghcr.io/plexara/api-test:v1.0.0 --version returns the stamped version/commit/date.

Wider gap I noticed but did not touch

While auditing for this PR I diff'd the trees against mcp-test. Listing what's missing here so you can decide:

  • Audit features: JSONB filter editor (pkg/audit/jsonfilter.go), live-tail SSE, audit replay (pkg/httpsrv/portal_api_replay_test.go), audit export, rate limiting (pkg/httpsrv/ratelimit.go), admin API (admin_api.go).
  • Portal pages: Compare.tsx, Wellknown.tsx. (Tools.tsx is MCP-specific; api-test's analogue is Endpoints.tsx.)
  • Migrations: 0002_audit_payloads.{up,down}.sql, 0003_audit_payloads_cleanup.{up,down}.sql.
  • Docs: operations/inspection.md, operations/kubernetes.md, getting-started/connect-client.md.
  • Tests: tests/audit_replay_test.go, audit_stream_test.go, portal_test.go, spa_test.go.
  • Dev: examples/ directory, scripts/screenshots/pnpm-lock.yaml.

Skipped from the gap because they're MCP-specific by definition: pkg/mcpmw/, pkg/tools/, docs/tools/, docs/reference/mcp.md. api-test's analogues live under pkg/endpoints/ and docs/endpoints/.

Mirrors mcp-test's release setup. Closes the M5-deferred gap that left
v1.x tags producing nothing on push.

`.github/workflows/release.yml` fires on tags matching `v*`, sets up Go
1.26.3 + Node 22 + pnpm 10 (so the embedded SPA is fresh in the
release binary), logs into GHCR with the workflow token, and runs
goreleaser. All third-party actions pinned to commit SHAs. Permissions
empty at the workflow level; the release job grants only contents:write
+ packages:write + id-token:write.

`.goreleaser.yml` (v2) builds darwin/{amd64,arm64} + linux/{amd64,arm64}
+ windows/amd64 with -trimpath -s -w and version metadata stamped via
-ldflags. Multi-arch GHCR image at ghcr.io/plexara/api-test, tagged
:latest, :{Tag}, :v{Major}. Changelog uses GitHub PR titles grouped by
feat/fix/security/others. The SPA before-hook builds ui/ via pnpm if
present so the embedded portal ships in every release artifact.

Dockerfile rewritten from a self-building distroless image to scratch +
ca-certs + a goreleaser-staged binary at linux/${TARGETARCH}/api-test.
The previous in-stage build path was mismatched against the existing
`make docker` target, which already pre-stages a binary the way
goreleaser does. HEALTHCHECK preserved (binary's --healthcheck flag).

`.dockerignore` matches mcp-test conventions. .gitignore adds /dist/
(goreleaser output) and /linux/ (`make docker` scratch dir).

Validated locally with `goreleaser check` and `goreleaser release
--snapshot --clean --skip=publish,docker,sign,before` — all 5
cross-platform binaries + archives + checksums produce in ~18s.
@cjimti cjimti merged commit cd1ddef into main May 10, 2026
7 checks passed
@cjimti cjimti deleted the add-release-workflow branch May 10, 2026 22:13
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