Skip to content

build(release): build the container image with melange + apko - #25

Merged
jmgilman merged 1 commit into
masterfrom
build/melange-apko
Jun 27, 2026
Merged

jmgilman merged 1 commit into
masterfrom
build/melange-apko

Conversation

@jmgilman

Copy link
Copy Markdown
Contributor

Summary

PR 2 of 2 in the dev-tooling migration. Replaces the hand-rolled multi-stage Dockerfile with melange (compiles the binary into a signed Wolfi apk) + apko (assembles a minimal, multi-arch, nonroot OCI image), and adds a keyless cosign signature. Builds on PR #24 (mise tooling). GoReleaser, the binary release assets, and Release Please are unchanged (GoReleaser still owns the downloadable binaries).

What changed

  • melange.yaml + apko.yaml (new) — go/build pipeline (-trimpath, -buildid=, -s -w, CGO_ENABLED=0; version/commit/date stamped via --vars-file); runtime is the distroless equivalent (uid 65532, ca-certificates-bundle, tzdata, no shell). The image is built without a Dockerfile.
  • release.yml — the two buildx jobs are replaced by: melange-build (native matrix amd64/ubuntu-24.04 + arm64/ubuntu-24.04-arm, no QEMU, per-arch ephemeral keys) → container-image-release (apko publish multi-arch index → 2-arch manifest assertion → smoke test → keyless cosign sign → attest-build-provenance → syft image SBOM + attest-sbom). binary-release-assets unchanged except setup-go → go.mod.
  • release-dry-run.yml + security-scan.yml — mirrored to melange/apko (dry-run builds + assembles without push/sign/attest; Trivy scans the apko image).
  • compose.yaml — drops build:; runs the prebuilt template-go-api:dev. New mise run image-local / mise run stack-up tasks build it locally (melange --runner docker, works on macOS). release-please-config.json gains extra-files to bump melange.yaml/apko.yaml.
  • Deleted Dockerfile, .dockerignore, .go-version.
  • Prose updated (README.md, CONTRIBUTING.md, DELETE_ME.md, docs/docs/index.md).

Base pinning decision

apko.lock.json was evaluated and not adopted: apko lock requires resolving the per-build @local app package (for all arches), and a committed lock would pin a stale app checksum that breaks the next release. Instead the Wolfi base floats to latest (fresh CA bundle/timezones, low CVE surface) and the exact resolved versions are captured in the per-build SBOM + provenance attestation — the idiomatic Wolfi posture.

Supply chain (preserve-or-better)

Control Before After
Binary SBOM / checksums / attest GoReleaser + syft + attest unchanged
Container SBOM BuildKit sbom:true apko default SBOM + syft image SBOM, both attested
Container provenance BuildKit provenance: mode=max attest-build-provenance on the index digest
Image signature none NEW: keyless cosign (Sigstore/Fulcio via OIDC)
apk signing n/a per-arch ephemeral key, private key never leaves its runner
Multi-arch completeness explicit 2-platform check retained (imagetools inspect)

Verification

  • Local end-to-end: melange build → apko build → docker run smoke (--version + openapi: 3.0.3), image runs as uid 65532, ~24 MB.
  • Day-one stack: mise run image-local + docker compose up → postgres/migrate/seed/api all healthy; GET /v1/todos with dev-user-key returns the seeded todos, 401 without a key.
  • moon run root:check green.
  • Caveat: the tag-triggered publish → cosign → attest path runs only on a tag/dispatch. Validate with release-dry-run (dispatch) and a throwaway prerelease tag against a scratch GHCR namespace before the first real release. docker buildx imagetools inspect relies on buildx being preinstalled on the runner (it is on ubuntu-24.04).

🤖 Generated with Claude Code

Replace the hand-rolled multi-stage Dockerfile with melange (compiles the binary
into a signed Wolfi apk) + apko (assembles a minimal, multi-arch, nonroot image).
GoReleaser still owns the binary release assets.

- melange.yaml + apko.yaml: go/build pipeline (-trimpath, -buildid=, -s -w,
  CGO_ENABLED=0; version/commit/date via --vars-file), distroless-equivalent
  runtime (uid 65532, ca-certificates, tzdata, no shell). The Wolfi base floats to
  latest (fresh CAs, low CVE); exact versions are recorded in the per-build SBOM +
  provenance attestation rather than pinned.
- release.yml container path: melange-build native matrix (amd64 + arm64, no QEMU)
  -> apko publish multi-arch index -> 2-arch manifest assertion -> keyless cosign
  sign -> attest-build-provenance -> syft image SBOM + attest-sbom. Per-arch
  ephemeral signing keys (distinct filenames; private key never leaves its runner).
  binary-release-assets unchanged except setup-go now reads go.mod.
- release-dry-run.yml + security-scan.yml mirrored to melange/apko.
- compose.yaml: drop build:, run the prebuilt template-go-api:dev image; new mise
  tasks image-local / stack-up build it locally (melange --runner docker, so it
  works on macOS). release-please extra-files bump melange.yaml + apko.yaml.
- Delete Dockerfile, .dockerignore, .go-version (both setup-go steps read go.mod).
- Prose updated (README/CONTRIBUTING/DELETE_ME/docs).

Verified locally: melange build -> apko assemble -> docker run smoke (--version +
openapi 3.0.3), nonroot uid 65532; and `mise run image-local` + `docker compose up`
brings up postgres/migrate/seed/api with GET /v1/todos returning the seeded todos
(401 without a key). The tag-triggered publish/sign/attest path is structurally
complete but is only fully exercised by a real tag (validate with a throwaway
prerelease tag against a scratch GHCR namespace before relying on a release).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@jmgilman
jmgilman merged commit 4098277 into master Jun 27, 2026
17 checks passed
@jmgilman
jmgilman deleted the build/melange-apko branch June 27, 2026 23:30
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.

2 participants