From def885985a68cbb4cee559e4de2ce65fd23fbde3 Mon Sep 17 00:00:00 2001 From: Dave Thomas <7sharp9@mail.com> Date: Tue, 25 Aug 2026 17:12:28 +0100 Subject: [PATCH 1/2] ci(github): publish the Docker image to GHCR on release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `publish-docker-image` to `tag-release.yml`, gated on `tag-and-release` succeeding, so a Docker image is only published for a commit that was actually tagged and released. - Builds `Dockerfile` at the same merge commit, `linux/amd64` only, `APP_VERSION` build-arg matching the local `DockerBuild` target, GHA layer caching. - Smoke tests the image (demo `GENPRES_URL_ID`, random throwaway password, `/` must return 200 within 60s) before pushing — verified locally against a real build, not just in CI. - Pushes `ghcr.io/informedica/genpres:`, plus `:latest` only on a stable (non-pre-release) version. GHCR is interim pending the `informedica` Docker Hub account (see issue thread); the registry is a single `IMAGE_NAME` env var so switching later is a small change. - Amends ADR-0021 with the decisions table, updates DEVELOPMENT.md's Release Automation section, adds a design-history change log entry. ## Follow-up needed after merge (not automatable) - **Package visibility**: the first push creates the GHCR package as private under `informedica`. Someone with org admin rights needs to set `informedica/genpres` public in GitHub package settings, or the image is unpullable without a token. - `Build.fs`'s local `DockerBuild` default (`halcwb/genpres`) is unchanged in this PR — separate decision. ## Test plan - [x] Built `Dockerfile` locally with the same `APP_VERSION` build-arg the workflow uses; build succeeded. - [x] Ran the built image with the demo `GENPRES_URL_ID` and a random password; `/` returned 200 on first poll, confirming the smoke-test step's assumptions (port 8085, startup timing). - [ ] First real run of `publish-docker-image` on the next release PR merge (cannot be dry-run outside GitHub Actions). Closes #234 item 3, addresses #459. --- .github/workflows/tag-release.yml | 133 +++++++++++++++++- DEVELOPMENT.md | 43 +++++- docs/mdr/design-history/0000-change-log.md | 1 + ...021-build-system-versioning-and-release.md | 39 ++++- 4 files changed, 206 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml index 5ac37b45..60fbbea7 100644 --- a/.github/workflows/tag-release.yml +++ b/.github/workflows/tag-release.yml @@ -1,11 +1,12 @@ name: Tag and Release -# This workflow turns a merged ShipIt release PR into a permanent release artifact: an annotated git tag -# on the merge commit and a GitHub Release with that version's section from CHANGELOG.md. +# This workflow turns a merged ShipIt release PR into permanent release artifacts: an annotated git tag +# on the merge commit, a GitHub Release with that version's section from CHANGELOG.md, and (issue #459) +# a Docker image published to GHCR and tagged with that same version. # -# This is a separate workflow, not a job inside build.yml or release.yml. The reason is the same as for +# This is a separate workflow, not a job inside build.yml or release.yml. The reason is the same as for # release.yml: a failure here must not stop the test and format matrix, and it runs on a different event -# (pull_request closed) than release.yml (push to master). See ADR-0021 and issue #470. +# (pull_request closed) than release.yml (push to master). See ADR-0021 and issues #470 and #459. # # The version number, pre-release flag, and release body all come from scripts/ReleaseNotes.fsx instead # of being parsed here. That means this workflow and a local dry run before merging a release PR use the @@ -49,6 +50,11 @@ jobs: github.event.pull_request.head.ref == 'release/master' runs-on: ubuntu-latest + outputs: + version: ${{ steps.release.outputs.version }} + tag: ${{ steps.release.outputs.tag }} + prerelease: ${{ steps.release.outputs.prerelease }} + steps: - name: Check the merge commit is known env: @@ -133,3 +139,122 @@ jobs: --notes-file "$RUNNER_TEMP/release-notes.md" \ ${flags[@]+"${flags[@]}"} echo "Published [$TAG](${{ github.server_url }}/${{ github.repository }}/releases/tag/$TAG)" >> "$GITHUB_STEP_SUMMARY" + + publish-docker-image: + name: Build, smoke test, and publish Docker image + # Runs after tag-and-release so it only fires for a real, verified release, and so it can reuse + # that job's version/tag/prerelease facts instead of re-deriving them from CHANGELOG.md a second + # time. GHCR is an interim registry: the project wants an `informedica` Docker Hub account + # (see issue #459), but that account does not exist yet, and GHCR needs no new secret, it + # authenticates with this workflow's own GITHUB_TOKEN. IMAGE_NAME is the one place the + # registry/namespace is named, so repointing at Docker Hub later is a one-line change here. + # + # Plain `docker` CLI throughout, not docker/build-push-action or docker/login-action: this repo + # already drives every other Docker operation (DockerBuild/DockerRun in Build.fs) with the CLI + # via FAKE, and this job builds once per release rather than once per PR, so a GHA layer-cache + # setup wasn't worth three more marketplace actions to maintain. `--platform` needs no separate + # buildx setup either — ubuntu-latest ships Docker Buildx preinstalled. + needs: tag-and-release + runs-on: ubuntu-latest + + # Job-level, not the workflow-level `contents: write` above: this job never writes repo + # contents, only reads the tagged commit and pushes a package, so it gets its own narrower grant + # rather than inheriting tag-and-release's. + permissions: + contents: read + packages: write + + env: + IMAGE_NAME: ghcr.io/informedica/genpres + + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + # Same merge commit tag-and-release tagged, so the image matches the tagged source exactly. + ref: ${{ github.event.pull_request.merge_commit_sha }} + + - name: Log in to GHCR + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: Determine image tags + id: tags + env: + VERSION: ${{ needs.tag-and-release.outputs.version }} + # String "true"/"false" from ReleaseNotes.fsx's %b formatter, not a YAML/shell boolean; + # compare it explicitly, same rule as the Create GitHub Release step above. + PRERELEASE: ${{ needs.tag-and-release.outputs.prerelease }} + # :latest only moves on a stable release. Every release shipped so far is a pre-release + # (0.1.2-alpha.N), so :latest stays unset until the first stable version ships. + run: | + set -euo pipefail + { + echo "tags<> "$GITHUB_OUTPUT" + + - name: Build image + # Same shape as buildDockerImage in Build.fs (the local `DockerBuild` target), just with + # multiple -t flags for the tags this release needs instead of one. + env: + TAGS: ${{ steps.tags.outputs.tags }} + VERSION: ${{ needs.tag-and-release.outputs.version }} + run: | + set -euo pipefail + tag_args=() + while IFS= read -r tag; do + [ -z "$tag" ] && continue + tag_args+=(-t "$tag") + done <<< "$TAGS" + # linux/amd64 only to start, matching DockerBuild's current default. Multi-arch is future + # scope, and a multi-platform image can't be loaded into the local daemon for the smoke + # test below the way a single-platform one can. + docker build --platform linux/amd64 --build-arg "APP_VERSION=$VERSION" "${tag_args[@]}" . + + - name: Smoke test the built image + env: + IMAGE: ${{ env.IMAGE_NAME }}:${{ needs.tag-and-release.outputs.version }} + # Public demo spreadsheet ID from .env.example. GENPRES_PROD defaults to 1 in the image, so + # the server refuses to start without both this and a 16+ char GENPRES_PASSWORD. + DEMO_URL_ID: 1IZ3sbmrM4W4OuSYELRmCkdxpN9SlBI-5TLSvXWhHVmA + # There is no dedicated health endpoint (see DEVELOPMENT.md), so "/" is the only smoke-test target + # available. This does not exercise medication calculations; it only proves the image starts and serves. + run: | + set -euo pipefail + password=$(openssl rand -base64 24) + container=$(docker run -d -p 8085:8085 \ + -e GENPRES_URL_ID="$DEMO_URL_ID" \ + -e GENPRES_PASSWORD="$password" \ + "$IMAGE") + cleanup() { + docker logs "$container" || true + docker rm -f "$container" >/dev/null 2>&1 || true + } + trap cleanup EXIT + + for _ in $(seq 1 30); do + if curl -sf -o /dev/null http://localhost:8085/; then + echo "Smoke test passed: / returned 200" + exit 0 + fi + sleep 2 + done + + echo "::error::Smoke test failed: / did not return 200 within 60s of container start" + exit 1 + + - name: Push image + # The image already built and passed its smoke test above, tagged locally under every tag + # from steps.tags. Pushing each tag directly here avoids building the image a second time. + env: + TAGS: ${{ steps.tags.outputs.tags }} + run: | + set -euo pipefail + while IFS= read -r tag; do + [ -z "$tag" ] && continue + docker push "$tag" + echo "Published \`$tag\`" >> "$GITHUB_STEP_SUMMARY" + done <<< "$TAGS" diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 2666ec62..3f792f31 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -426,9 +426,9 @@ and [issue #470](https://github.com/informedica/GenPRES/issues/470)). The workfl 4. Creates a GitHub Release for the tag, with the extracted section as the body, flagged pre-release when the version is a SemVer pre-release (`0.1.2-alpha.4` is, `0.1.3` is not). -Both steps are idempotent: an existing tag or Release is left alone, so re-running is safe. No build output -is attached — the tag plus the changelog body is the whole artifact; publishing built images is -[#459](https://github.com/informedica/GenPRES/issues/459)'s scope. +Both steps are idempotent: an existing tag or Release is left alone, so re-running is safe. The tag and +Release carry no attached build output — the Docker image built from the same merge commit is published +separately by the `publish-docker-image` job; see [Publishing the Docker image](#publishing-the-docker-image). The tag record starts at the first release after this workflow landed. `0.1.2-alpha.2`, `.3` and `.4` shipped before it existed and are deliberately not backfilled, so they have no tag and no Release page; @@ -481,6 +481,43 @@ and reopened by hand. A workflow keyed on `on: release` or `on: push: tags:` the options for anything downstream are a job inside `tag-release.yml`, a `workflow_dispatch` / `repository_dispatch` call (the two events explicitly exempt from the rule), or a PAT / GitHub App token. +#### Publishing the Docker image + +A `publish-docker-image` job in `tag-release.yml`, gated on `needs: tag-and-release`, closes +[#234](https://github.com/informedica/GenPRES/issues/234) item 3 +([#459](https://github.com/informedica/GenPRES/issues/459)) — see +[ADR-0021's Docker image publishing amendment](docs/mdr/design-history/0021-build-system-versioning-and-release.md) +for the full design rationale. It only runs once tagging and the Release have both succeeded, and reuses +that job's `version`/`tag`/`prerelease` outputs. For a given release it: + +1. Checks out the same merge commit `tag-and-release` tagged. +2. Builds the `Dockerfile` with `--build-arg APP_VERSION=` (same as the local `DockerBuild` FAKE + target), `linux/amd64` only, tagging every tag the release needs in one `docker build -t ... -t ...` call. +3. Starts the built image with the public demo `GENPRES_URL_ID` (from `.env.example`) and a random + per-run `GENPRES_PASSWORD`, and requires `/` to return 200 within 60 seconds before treating the image as good. +4. Pushes `ghcr.io/informedica/genpres:`, and also `:latest` when the version is a stable release + (currently we only ship alphas, so `:latest` stays unpublished). + +Registry is GHCR, as an interim step. The project's preferred home is a Docker Hub `informedica` account, +which is pending. GHCR needs no new secret, it authenticates with the workflow's own `GITHUB_TOKEN`, and the +registry/namespace is a single `IMAGE_NAME` job-level env var in `tag-release.yml`, so switching to Docker Hub +later is a small follow-up. + +The job drives Docker with plain `docker login`/`docker build`/`docker push`, not `docker/login-action`, +`docker/setup-buildx-action`, or `docker/build-push-action`. Everywhere else in this repo Docker goes through +the CLI (`Build.fs`'s `DockerBuild`/`DockerRun`), and this job runs once a release rather than once a PR, so a +GHA layer cache wasn't worth three more marketplace actions to pin and keep updated. `ubuntu-latest` ships +Buildx preinstalled, so `--platform` still works with no setup step. + +**Package visibility is a manual step.** A container package pushed to an organization's GHCR for the +first time from a workflow defaults to **private**. Since GenPRES is public, someone with org admin +rights needs to set `informedica/genpres` to public in GitHub's package settings after the first +successful push — the workflow's `GITHUB_TOKEN` can't change package visibility itself. + +To build and smoke test the same image locally before relying on the workflow, use the existing +`DockerBuild`/`DockerRun` FAKE targets (see [Docker wrappers](#docker-wrappers) above); they build +`halcwb/genpres` by default (override with `DOCKER_IMAGE`), separate from what the workflow publishes. + ### IDE Integration #### Visual Studio Code diff --git a/docs/mdr/design-history/0000-change-log.md b/docs/mdr/design-history/0000-change-log.md index e5e1c935..d6ad13c7 100644 --- a/docs/mdr/design-history/0000-change-log.md +++ b/docs/mdr/design-history/0000-change-log.md @@ -22,6 +22,7 @@ Maintain this document as a reverse-chronological log of significant design chan | Date | ADR | Summary | |------|-----|---------| +| 2026-08-25 | [ADR-0021](0021-build-system-versioning-and-release.md) | Amended with the Docker image publishing decision: a `publish-docker-image` job in `tag-release.yml`, gated on the tag-and-release job succeeding, builds, smoke tests, and pushes `ghcr.io/informedica/genpres:` (interim pending an `informedica` Docker Hub account); `:latest` moves only on a stable release. See issue #459 | | 2026-08-19 | [ADR-0021](0021-build-system-versioning-and-release.md) | Amended with the release-artifact decision: `tag-release.yml` creates a `v`-prefixed annotated tag on the release PR's merge commit and publishes a GitHub Release carrying that version's changelog section. No backfill: the tag record starts at the next release. See issue #470 | | 2026-08-17 | [ADR-0021](0021-build-system-versioning-and-release.md) | Build system versioning and release automation accepted; EasyBuild.ShipIt owns version/changelog/release-PR generation and writes `Directory.Build.props`, all three merge methods left enabled with `--skip-merge-commit`, Repo Assist Task 8 retired, Docker-on-release and API docs deferred to #459/#460. See issue #234 | | 2026-08-05 | [ADR-0021](0021-build-system-versioning-and-release.md) | Build system versioning and release automation proposed. See issue #234 | diff --git a/docs/mdr/design-history/0021-build-system-versioning-and-release.md b/docs/mdr/design-history/0021-build-system-versioning-and-release.md index 1692e909..880e8ce4 100644 --- a/docs/mdr/design-history/0021-build-system-versioning-and-release.md +++ b/docs/mdr/design-history/0021-build-system-versioning-and-release.md @@ -147,6 +147,31 @@ every release PR merged as a true merge commit whose push event carries cannot be an `on: release` workflow; it must be a job in `tag-release.yml`, a `workflow_dispatch` / `repository_dispatch` call, or use a PAT / GitHub App token. +### Docker image publishing — amended 2026-08-25 + +This ADR pushed #234 item 3 ("publish a Docker image on release") into [issue #459](https://github.com/informedica/GenPRES/issues/459). +Up to now, every release image was built and pushed manually with `dotnet run DockerBuild`, so the published tag didn't always match the +commit that produced the `CHANGELOG.md` entry. + +**Decision**: a second job, `publish-docker-image`, is added to `tag-release.yml`. It waits for `tag-and-release`, +builds the `Dockerfile` at the same merge commit that was tagged, smoke-tests the image, and then pushes it. It +uses the `version`/`tag`/`prerelease` outputs from the first job instead of recalculating anything. It only runs +when both tagging and the GitHub Release succeed, so a broken commit never publishes an image. + +Key points agreed during implementation: + +| Question | Decision | +|---|---| +| Registry | Use `ghcr.io/informedica/genpres` for now. The preferred long-term home is a Docker Hub `informedica` account, requested from `@jennifervdstreek` on 2026-08-21 and still pending. GHCR needs no new secret because it uses the workflow's `GITHUB_TOKEN`. The registry/namespace is set in one `IMAGE_NAME` env var, so switching to Docker Hub later is a simple one-line change. | +| Tags pushed | Always push `:` (e.g. `:0.1.2-alpha.6`). `:latest` only moves on a stable (non-pre-release) version. All releases so far are `0.1.2-alpha.N`, so `:latest` stays empty until the first stable version. The pre-release flag comes from `needs.tag-and-release.outputs.prerelease`, the same string the GitHub Release step already checks. | +| Architecture | Only `linux/amd64`, matching the existing `DockerBuild` FAKE target. Multi-arch is left for later: a multi-platform build produces a manifest list rather than one runnable image, and the smoke test below needs to `docker run` the image locally, so going multi-arch means reworking that step too. | +| Build-time secrets | None. `APP_VERSION` is the only build arg, same as `DockerBuild`. `GENPRES_URL_ID` and `GENPRES_PASSWORD` are runtime-only and left empty in the Dockerfile. They are never passed as build args, keeping the rule from `DEVELOPMENT.md` that build args must not bake secrets into image metadata. | +| Gate before publish | The image is built once, tagged with every tag it needs in a single `docker build -t ...` call, then started using the public demo `GENPRES_URL_ID` from `.env.example` and a random `GENPRES_PASSWORD`. The SPA shell (`/`) must return 200 within 60 seconds. Only then are the tags pushed with `docker push`, so nothing gets built twice. Not a full functional test, but it does catch images that fail to start — something the old manual process never checked. | +| Docker tooling | Plain `docker login` / `docker build` / `docker push`, not `docker/login-action`, `docker/setup-buildx-action`, or `docker/build-push-action`. This matches how `Build.fs`'s `DockerBuild` target already drives Docker everywhere else in this repo, and it's three fewer marketplace actions to pin and keep updated for a job that runs once a release, not once a PR — the GHA layer cache those actions unlock isn't worth much at that frequency. `ubuntu-latest` ships Buildx preinstalled, so `--platform` still works with no setup action. | +| Package visibility | Not decided here. A first-time GHCR push from a workflow creates a **private** package visible only to the repo. Since GenPRES is public and the image must be pullable, `@halcwb` or `@jennifervdstreek` need to set `informedica/genpres` to public in the GitHub org package settings. The workflow can't change visibility itself. | + +**Accepted trade-off**: GHCR is temporary. Once the `informedica` Docker Hub account exists, the follow-up is a small PR that changes the `IMAGE_NAME` value and swaps the `docker login` call to Docker Hub credentials — a new secret, unlike GHCR's `GITHUB_TOKEN`. Keeping this amendment focused means the workflow stays simple and uses one registry for now. + ## Consequences **Positive**: @@ -160,6 +185,8 @@ cannot be an `on: release` workflow; it must be a job in `tag-release.yml`, a `w - Every version shipped under ShipIt has an immutable tag and a Release page carrying its changelog section, so "what was 0.1.2-alpha.3" is answerable from a ref rather than by hand-resolving commit SHAs out of `CHANGELOG.md`. +- Every published Docker tag now corresponds to the exact merge commit `tag-and-release` tagged and + passed a startup smoke test, closing the gap where a hand-pushed image had no relationship to a shipped version. **Negative / Trade-offs**: @@ -171,11 +198,16 @@ cannot be an `on: release` workflow; it must be a job in `tag-release.yml`, a `w entry today) become leaner, commit-title-derived entries under ShipIt. A `=== changelog ===` block in the commit message body is the escape hatch for entries that need more detail than a title provides. -- Items 3 and 4 remain unaddressed after #234 closes; they need their own - issues and, eventually, their own ADRs or ADR amendments. +- Item 4 (API docs, [#460](https://github.com/informedica/GenPRES/issues/460)) remains + unaddressed after #234 closes; it needs its own issue work and, eventually, its own ADR or + ADR amendment. Item 3 (Docker-on-release) is closed by the "Docker image publishing" amendment above. - The tag and Release are created with the workflow's own `GITHUB_TOKEN`, so nothing can chain off them with `on: release` or `on: push: tags:`. Any future release-time automation has to live inside `tag-release.yml`, be dispatched explicitly, or use a PAT / App token. +- GHCR is an interim registry, not the project's preferred long-term home ([#459](https://github.com/informedica/GenPRES/issues/459)); + moving to Docker Hub once the `informedica` account exists is a deliberate follow-up, not + automatic. Package visibility is also a manual step outside the workflow's `GITHUB_TOKEN` + permissions — see the "Package visibility" row above. **MDR / Safety**: @@ -197,7 +229,8 @@ cannot be an `on: release` workflow; it must be a job in `tag-release.yml`, a `w - [Issue #234 — Improve build system](https://github.com/informedica/GenPRES/issues/234) - [Issue #470 — Tag and publish a GitHub Release when the ShipIt release PR merges](https://github.com/informedica/GenPRES/issues/470) (follow-up, item 2, second half) -- [Issue #459 — Publish the Docker image automatically on release](https://github.com/informedica/GenPRES/issues/459) (follow-up, item 3) +- [Issue #459 — Publish the Docker image automatically on release](https://github.com/informedica/GenPRES/issues/459) (follow-up, item 3, closed by the Docker image publishing amendment above) +- [`.github/workflows/tag-release.yml`](../../../.github/workflows/tag-release.yml) — implements both the tag/Release job and the `publish-docker-image` job - [Issue #460 — Auto-generate and publish API documentation](https://github.com/informedica/GenPRES/issues/460) (follow-up, item 4) - [Implementation plan for issue #234](../../implementation-plans/234-improve-build-system.md) - [EasyBuild.ShipIt](https://github.com/easybuild-org/EasyBuild.ShipIt) From 811bacc013da55c925f3189729aa99d85b0c2269 Mon Sep 17 00:00:00 2001 From: Dave Thomas <7sharp9@mail.com> Date: Tue, 25 Aug 2026 17:22:23 +0100 Subject: [PATCH 2/2] fix(github): fold + to - in Docker version tags Versioning.fsx's isPreRelease already tolerates SemVer build metadata in (a `+build.N` suffix), even though no release has shipped one yet. Docker tags reject `+` outright, so a version like `1.0.0+build.7` would flow straight into `$IMAGE_NAME:$VERSION`, and the Git tag and GitHub Release for that version would already exist by the time `docker build` rejected the tag as invalid. Fold `+` to `-` once, in the Determine image tags step, and expose the result as its own version_tag output. The smoke test step now reads that output instead of rebuilding a tag from the raw version, which was quietly wrong the same way: it would have run against a tag docker never actually built. Caught by Greptile's review of #482. ADR-0021's Tags pushed row and DEVELOPMENT.md's publish-docker-image steps note why the fold exists. Refs #459 --- .github/workflows/tag-release.yml | 15 +++++++++++++-- DEVELOPMENT.md | 4 +++- .../0021-build-system-versioning-and-release.md | 2 +- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml index 60fbbea7..bf391e1a 100644 --- a/.github/workflows/tag-release.yml +++ b/.github/workflows/tag-release.yml @@ -187,9 +187,18 @@ jobs: # (0.1.2-alpha.N), so :latest stays unset until the first stable version ships. run: | set -euo pipefail + # scripts/Versioning.fsx's isPreRelease explicitly allows SemVer build metadata + # (a "+..." suffix) in , even though no release has used one yet. Docker tags + # can't contain "+" (grammar is [a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}), so a version like + # 1.0.0+build.7 would otherwise build a tag docker build rejects, after the Git tag and + # GitHub Release for that version already exist. Fold "+" to "-" so the image tag always + # stays valid. + docker_version="${VERSION//+/-}" + version_tag="$IMAGE_NAME:$docker_version" + echo "version_tag=$version_tag" >> "$GITHUB_OUTPUT" { echo "tags<`, and also `:latest` when the version is a stable release - (currently we only ship alphas, so `:latest` stays unpublished). + (currently we only ship alphas, so `:latest` stays unpublished). Any `+` in `` is folded to `-` + first: `Versioning.fsx` allows SemVer build metadata in ``, but a raw `+` isn't a legal Docker + tag character. Registry is GHCR, as an interim step. The project's preferred home is a Docker Hub `informedica` account, which is pending. GHCR needs no new secret, it authenticates with the workflow's own `GITHUB_TOKEN`, and the diff --git a/docs/mdr/design-history/0021-build-system-versioning-and-release.md b/docs/mdr/design-history/0021-build-system-versioning-and-release.md index 880e8ce4..17c02b5e 100644 --- a/docs/mdr/design-history/0021-build-system-versioning-and-release.md +++ b/docs/mdr/design-history/0021-build-system-versioning-and-release.md @@ -163,7 +163,7 @@ Key points agreed during implementation: | Question | Decision | |---|---| | Registry | Use `ghcr.io/informedica/genpres` for now. The preferred long-term home is a Docker Hub `informedica` account, requested from `@jennifervdstreek` on 2026-08-21 and still pending. GHCR needs no new secret because it uses the workflow's `GITHUB_TOKEN`. The registry/namespace is set in one `IMAGE_NAME` env var, so switching to Docker Hub later is a simple one-line change. | -| Tags pushed | Always push `:` (e.g. `:0.1.2-alpha.6`). `:latest` only moves on a stable (non-pre-release) version. All releases so far are `0.1.2-alpha.N`, so `:latest` stays empty until the first stable version. The pre-release flag comes from `needs.tag-and-release.outputs.prerelease`, the same string the GitHub Release step already checks. | +| Tags pushed | Always push `:` (e.g. `:0.1.2-alpha.6`). `:latest` only moves on a stable (non-pre-release) version. All releases so far are `0.1.2-alpha.N`, so `:latest` stays empty until the first stable version. The pre-release flag comes from `needs.tag-and-release.outputs.prerelease`, the same string the GitHub Release step already checks. `` has any `+` folded to `-` before it becomes a tag: `Versioning.fsx`'s `isPreRelease` already tolerates SemVer build metadata (`1.0.0+build.7`), and a raw `+` isn't a legal Docker tag character, so an untranslated build-metadata version would fail the image build after the Git tag and Release for it already exist. Caught by Greptile's review of PR. | | Architecture | Only `linux/amd64`, matching the existing `DockerBuild` FAKE target. Multi-arch is left for later: a multi-platform build produces a manifest list rather than one runnable image, and the smoke test below needs to `docker run` the image locally, so going multi-arch means reworking that step too. | | Build-time secrets | None. `APP_VERSION` is the only build arg, same as `DockerBuild`. `GENPRES_URL_ID` and `GENPRES_PASSWORD` are runtime-only and left empty in the Dockerfile. They are never passed as build args, keeping the rule from `DEVELOPMENT.md` that build args must not bake secrets into image metadata. | | Gate before publish | The image is built once, tagged with every tag it needs in a single `docker build -t ...` call, then started using the public demo `GENPRES_URL_ID` from `.env.example` and a random `GENPRES_PASSWORD`. The SPA shell (`/`) must return 200 within 60 seconds. Only then are the tags pushed with `docker push`, so nothing gets built twice. Not a full functional test, but it does catch images that fail to start — something the old manual process never checked. |