Skip to content

ci(docker): publish multi-arch Docker images from a manual workflow - #138

Open
wayfarer3130 wants to merge 17 commits into
masterfrom
ci/docker-publish
Open

wayfarer3130 wants to merge 17 commits into
masterfrom
ci/docker-publish

Conversation

@wayfarer3130

@wayfarer3130 wayfarer3130 commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR adds a manual workflow that publishes the Docker image for linux/amd64 and linux/arm64. The PR also merges Dockerfile and arm-Dockerfile into one Dockerfile, and makes the image install its packages from pnpm-lock.yaml. A new CI workflow builds and tests the image for both architectures in a pull request that changes the files of the image.

The publish workflow builds release tags from the publish workflow of #137. The first tag that it can build is the first release after the merge of this PR.

Security fix: the image did not use the lockfile

The old installer stage ran npm install on the packed packages. npm ignored pnpm-lock.yaml and the overrides of pnpm-workspace.yaml, and resolved each dependency again.

  • A local build of 1.7.7 with the old Dockerfile held 21 package versions that are not in the lockfile.
  • These versions include adm-zip 0.5.17 and 0.5.18, with 5 high advisories (for example GHSA-8238-w5pm-2374). The override pins adm-zip to 0.6.1.
  • dcmjs-dimse also brought dcmjs 0.38.3. The override pins dcmjs to 0.52.0.
  • npm got @radicalimaging/static-wado-plugins from the registry with the range >=1.7.7. A later build of the same tag could get a newer version.
  • The published images up to 1.7.6 come from the same installer stage, so they very probably have the same problem.

The builder now runs pnpm deploy --prod for @radicalimaging/static-wado-webserver. On amd64 and on arm64, the image now holds no package version that is not in pnpm-lock.yaml.

Manual release of 1.7.7

A maintainer built 1.7.7 by hand from this branch (the code of v1.7.7 and the Docker changes of this PR at that time), and pushed it to Docker Hub:

  • braveheartsoftware/static-dicomweb:1.7.7 and :latest are one multi-arch index (sha256:c932b0bf9d27…) for linux/amd64 and linux/arm64, with attestations.
  • The build of arm64 ran under QEMU on an amd64 host.
  • The arm64 image from Docker Hub holds adm-zip 0.6.1.
  • That image has the OCI labels of oven/bun (for example org.opencontainers.image.source=https://github.com/oven-sh/bun and version=1.3.13), because the Dockerfile set no labels then. The current Dockerfile sets the labels of this project. The 1.7.7 image keeps the old labels; the next release gets the correct ones.
  • GHCR has no image yet. The first run of the workflow creates the GHCR package.

Changes for users

  • The images are multi-arch from 1.7.7: one tag (for example 1.7.7 or latest) holds linux/amd64 and linux/arm64, and Docker selects the correct architecture for the host. Before, Docker Hub had amd64 images only. An arm64 image existed only as a local build (pnpm run docker:build:arm), with the local tag :arm.
  • The DIMSE SCP is not in the image. The old tag v1 (2024-11-06, amd64) started /app/startStaticDicomweb.sh, which ran dicomwebserver and dicomwebscp scp -p 11115. No image since at least 1.7.6 contains dicomwebscp, and the default command runs only monitordicomwebserver.
    • docker/docker-compose.yml moves from v1 to latest, and drops the mapping of port 11115. A compose user who used the SCP on 11115 loses it.
    • The README no longer maps a DIMSE port in its docker run example, and says that the image has no SCP.
  • docker/docker-compose.yml does not force platform: linux/amd64 now, so an arm64 host runs the image without emulation.
  • An npm release does not publish an image. A maintainer must start the workflow.
  • The workflow makes only the tags X.Y.Z and latest. It makes no X or X.Y tags.
  • A version keeps one image. A run for a version that a registry holds with another image fails, unless the maintainer sets overwrite.
  • latest moves only forward. A run for a release moves latest when latest holds an older version, also when a newer tag on master has no image yet. A re-run of an older release does not move latest back.
  • The images of the workflow carry the OCI labels and index annotations of this project: title, description, url, source, documentation, licenses (MIT), version, revision, and created (the time of the release commit).
  • The arm64 image has the same default command as the amd64 image: monitordicomwebserver. The old arm-Dockerfile used dicomwebserver, and had no curl and no EXPOSE 6499.
  • The images of releases after 1.7.7 are also on GHCR: ghcr.io/radicalimaging/static-dicomweb.
  • /app in the image now holds the deployed static-wado-webserver package (bin/, lib/, dist/, package.json, pnpm-lock.yaml), not an npm project with five file: dependencies and a package-lock.json. The commands createdicomweb, mkdicomweb, dicomwebserver and monitordicomwebserver stay on PATH. node_modules/.bin no longer has the dev tools of the old image (for example acorn, terser, webpack).
  • pnpm run docker:build:arm now builds a real linux/arm64 image. On an amd64 host, the build needs QEMU. Before, the script built an image for the architecture of the host.
  • The local scripts docker:build, docker:build:arm, docker:run and docker:dicomwebserver now use the local tags static-dicomweb:dev and static-dicomweb:dev-arm64, not braveheartsoftware/static-dicomweb:latest. A local build then cannot replace a release by accident.
  • /app/startStaticDicomweb.sh now runs only monitordicomwebserver, and git marks it executable (100755), so an image built on Linux can run it. Before, it also started dicomwebscp, which the image does not contain.

Implementation

Dockerfile

  • The builder uses node:24-trixie, and the final stage uses oven/bun:1.3.13. Each image is pinned by digest. Both images use Debian trixie, so the canvas that arm64 compiles finds the same libraries at run time.
  • The Dockerfile frontend (docker/dockerfile:1.7-labs) is also pinned by digest.
  • The apt packages are not pinned, because Debian removes old versions from trixie, and a pinned version then stops installing. Each apt-get install uses --no-install-recommends, and the final stage installs ca-certificates explicitly.
  • The final stage runs apt-get upgrade, because the pinned oven/bun image predates the trixie security updates. docker scout on the amd64 image: before, 5 critical and 15 high findings (perl, glibc, pcre2, sqlite3 and others); after, 0 critical and 3 high. The 3 have no fixed version (zlib, cyrus-sasl2), or are the accepted braces advisory of pnpm-workspace.yaml. The published 1.7.7 image has the old packages.
  • canvas 3.1.0 has no linux-arm64 prebuilt binary, so arm64 compiles canvas from source. The builder installs the canvas build packages on arm64 only. node:24-trixie has all of them except libgif-dev, but the list names each one.
  • On amd64, canvas uses the prebuilt binary, which bundles its libraries. If the download fails, canvas compiles against the libraries of node:24-trixie, and the final stage does not have them. The final stage runs require('canvas').createCanvas(1, 1), so such an image fails the build, also in a local build.
  • The global install of pnpm keeps its install scripts. Without them, pnpm runs its Node launcher, which gives no node-gyp to the canvas build on arm64. A local arm64 build with --ignore-scripts failed with node-gyp: not found. The pnpm version must stay equal to packageManager in package.json; corepack is not an option, because Node removes it after version 24.
  • The build uses the filter @radicalimaging/static-wado-webserver..., so the build covers the server and the 5 workspace packages that it needs. These are the packages that pnpm deploy copies.
  • The final stage links dicomwebserver and monitordicomwebserver into node_modules/.bin, because pnpm deploy links only the commands of the dependencies.
  • The final stage replaces the OCI labels of oven/bun. The build arguments IMAGE_VERSION, IMAGE_REVISION and IMAGE_CREATED set the release values; a local build gets dev, unknown and unknown.
  • .dockerignore excludes the testdata submodule. The docker:build scripts no longer run cleanTgz.

Workflow .github/workflows/docker-publish.yml

  • workflow_dispatch is the only trigger. The inputs:
    • tag: optional. An empty value selects the newest vX.Y.Z tag on master.
    • ghcr_only: skip Docker Hub.
    • overwrite: replace the image of a version in both registries. The resolve job refuses overwrite together with ghcr_only, because that run gives the two registries two images for one version.
  • A run that does not start on master fails. It does not show a green run that publishes nothing.
  • The resolve job runs scripts/release/docker-release.mjs:
    • The script uses only the release tags that master contains.
    • The tag must carry its version in packages/create-dicomweb/package.json.
    • The Dockerfile of the tag must have a RUN line with pnpm … deploy --prod. The script refuses v1.7.7 and older tags with a clear error, so a run cannot build the old Dockerfile.
    • The error messages quote the input in JSON, so a newline in the input cannot start a workflow command.
  • The build job checks out the commit SHA from resolve, and builds on a native runner (ubuntu-24.04 and ubuntu-24.04-arm).
    • The BuildKit daemon (moby/buildkit:v0.34.0) is pinned by digest, because it runs the build and holds the push credential.
    • Each build pushes an untagged image by digest to GHCR with GITHUB_TOKEN, with SBOM and provenance attestations. The label created is the time of the release commit. A rebuild gives the same label, but after apt-get upgrade it can hold newer packages.
    • Each build then runs scripts/docker-smoke-test.sh. canvas must make a PNG, createdicomweb --help and mkdicomweb --help must succeed, the other commands must exist, and the default command must answer GET /dicomweb/studies within 180 seconds (an arm64 image under QEMU starts slowly). Each request has a limit of 5 seconds. SMOKE_TIMEOUT_SECONDS raises the wait for a local QEMU run; under QEMU the monitor of the image can restart the slow server first. A failed test stops the run before the approval, so a broken image gets no tag.
  • The publish job uses the docker-hub environment, on the pinned runner ubuntu-24.04, with contents: read and packages: write. The run stops at this job until a reviewer approves the deployment.
    • Only this job can read DOCKERHUB_TOKEN. When the secret is not set and ghcr_only is not set, the job fails. Only a job of the environment can see the secret, so this check runs after the approval.
    • The job checks out scripts/docker-publish-tags.sh from master (the dispatch ref) and runs it. The job runs no other code of the repository, and no BuildKit daemon.
  • scripts/docker-publish-tags.sh writes the tags:
    • When GHCR holds the version already (a run that stopped before Docker Hub, or a ghcr_only run), the script publishes that image and not the new build. The revision label of each platform image must be the release commit, because GHCR is not behind the approval. Otherwise the run fails, unless overwrite is set.
    • A tag that holds the same manifests counts as done, so "Re-run failed jobs" continues. A tag that holds another image stops the run, unless overwrite is set.
    • latest moves only forward: each run moves latest, unless the version label of the current latest is a newer X.Y.Z. The newest tag on master does not decide it, because an npm release does not publish an image, so an older release can still move latest forward. Another label (for example dev of a local build, which sort -V puts above every number) does not hold latest.
    • The script refuses a digest file name that is not 64 hex characters, and uses inherit_errexit, so a failed read inside $(...) stops the script.
    • Only the answer "not found" counts as a free tag. Any other error of the registry stops the run.
    • The script adds the OCI annotations to the index, and then checks that each new tag holds the expected manifests.
    • A manual push between the checks and the write is not detected. Do not push these tags by hand.
  • The workflow follows the rules of publish.yml: actions pinned by commit, no build cache, and permissions: {} at the top level. The digest artifacts last 30 days, because an approval can wait 30 days. The repository allows 90 days.

Workflow .github/workflows/docker-ci.yml

  • A pull request that changes Dockerfile, .dockerignore, docker/, package.json, tsconfig.json, tsconfig.base.json, babel.config.js, pnpm-lock.yaml, pnpm-workspace.yaml, packages/, the Docker scripts or a Docker workflow starts this workflow. A manual start is also possible.
  • The image job builds the image for amd64 and arm64 on native runners without a push, and runs scripts/docker-smoke-test.sh. A broken image then shows up before a release, and not after npm holds the version.
  • The publish-tags job runs shellcheck on the Docker scripts, and runs scripts/docker-publish-tags.test.sh against a local registry.

Limits

  • The environment gates Docker Hub only. GHCR takes GITHUB_TOKEN, so any workflow of this repository with packages: write can write to the GHCR package, from any branch. The revision check limits what can reach Docker Hub from GHCR, but a forged label can pass it.
  • "Prevent self-review" is off, so one maintainer can start a run and approve it.
  • Docker Hub cannot limit a personal access token to one repository. The token can write to every repository of braveheartsoftware.
  • A failed or rejected run leaves untagged digests in GHCR.
  • Docker Hub has OIDC (trusted publishing) for GitHub Actions since July 2026, but only for organization accounts on the Team, Business, DHI or Docker-Sponsored Open Source plans. braveheartsoftware is a personal account. A later move to an organization changes only the Docker Hub login step. Because the publish job names an environment, the OIDC subject is repo:RadicalImaging/Static-DICOMWeb:environment:docker-hub, and the Docker Hub ruleset must match that subject.

Setup before the first run

  1. On Docker Hub, sign in as braveheartsoftware. Create a personal access token with the access "Read & Write".
  2. On GitHub, create the environment docker-hub. Add a required reviewer, and keep "Prevent self-review" off.
  3. In docker-hub, set "Deployment branches and tags" to "Selected branches and tags", with the branch master only.
  4. In docker-hub, add the secret DOCKERHUB_TOKEN. The variable DOCKERHUB_USERNAME is optional, and the default is braveheartsoftware.
  5. After the first run, open the package static-dicomweb on GitHub, and change the visibility to public. GHCR creates a new package as private. Do not push to GHCR by hand before the first run.

Tests

  • release.test.mjs has two new tests, both in the category "the API must do this":

    • chooseRelease: the selection of the newest tag, the numeric order, and the refusal of a tag that is not a release tag.
    • findTagProblems: a wrong version, a missing package.json, a Dockerfile without the lockfile install, and a lockfile install in a comment only.
  • scripts/docker-publish-tags.test.sh ("the API must do this") runs the tag script against a local registry on 127.0.0.1, with small FROM scratch images per architecture that carry attestations and labels. It has 18 checks:

    • a ghcr_only publish leaves Docker Hub alone; ghcr_only with overwrite and a bad digest file name are refused;
    • a later full run publishes the GHCR image, so both registries and latest hold one image;
    • a re-run continues;
    • a GHCR version from another commit is refused;
    • Docker Hub with another image is refused, and overwrite replaces it in both registries;
    • a newer release moves latest to that release, and a re-run of an older release leaves latest alone;
    • a dev label on latest does not hold latest.

    Each refusal check also matches the error message. Mutation tests: a copy of the script without the revision check and without the guard of latest fails exactly those two checks, and a copy without the X.Y.Z filter fails exactly the dev check.

  • docker-release.mjs on this repository refuses an empty input and v1.7.7 (old Dockerfile), v1.7.6 (master does not contain that tag) and v1.5.0.

  • scripts/docker-smoke-test.sh passes on the local images, and fails with exit code 127 on an image without the commands.

  • Local builds of this branch for linux/amd64 and linux/arm64 (QEMU) succeed, with the labels of this project. The smoke test passes on amd64, and on arm64 under QEMU with SMOKE_TIMEOUT_SECONDS=600 (302 seconds). In the images of the previous commit, HTTPS works and each package version is in pnpm-lock.yaml.

  • actionlint reports no errors for both workflows. shellcheck 0.9.0 (the version of the ubuntu-24.04 runner) and 0.11.0 report no errors for the three Docker scripts and docker/startStaticDicomweb.sh. An earlier commit of this PR failed in CI, because it disabled only SC2329, the newer code of SC2317.

  • docker-publish.yml did not run. It runs only from master, and it needs a release tag that contains the new Dockerfile. docker-ci.yml runs in this PR.

🤖 Generated with Claude Code

wayfarer3130 and others added 8 commits October 6, 2026 11:30
…h audit findings

- @cornerstonejs/core 4.22.3 -> 5.11.4 (adds metadata/utils peers),
  dicom-codec 1.1.6, codec-openjph 2.4.11, codec-openjpeg 1.3.6
- dcmjs 0.50.1 -> 0.52.0
- Fix high/critical bun audit findings: aws-cdk-lib 2.272.0 (cli 2.1144.0),
  ws 8.22.0, adm-zip 0.6.1, overrides for axios, fast-uri, js-yaml, pacote,
  smol-toml, tar; re-resolve other transitive deps within their ranges
- cs3d: compile with module node20 so it can import the ESM-only core 5 types
- jest: transform gl-matrix, which core 5 installs nested
- bunfig: pin linker = "hoisted" (bun.lock configVersion 1 defaults to
  isolated) and apply minimumReleaseAge to install:update-lockfile

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… 5.11.5

Bun does not accept a scope wildcard in minimumReleaseAgeExcludes, so the
excludes list each @cornerstonejs package in the dependency tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fixable audit advisories

- Override http-cache-semantics to 4.3.0 to clear the lerna advisory.
- Ignore four dev-only DoS advisories in the CI audit step: braces has no
  patched release, and nx pins brace-expansion 5.0.8 exactly.
- Bump constructs to 10.8.1 to meet the aws-cdk-lib peer range ^10.5.0.
- Note in both bunfig files that the Cornerstone3D exclude lists must match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed publishing

- Replace bun and lerna with pnpm 12.9.1 for install, lockfile, scripts and audit.
  Bun stays the runtime for the CLIs and the Docker service image.
- pnpm-workspace.yaml holds the overrides, the release-age rule (with a
  @cornerstonejs/* exclude), allowBuilds and auditConfig.ignoreGhsas. Range
  overrides fix brace-expansion, so only braces (no patched release) is ignored.
- CI uses pnpm/action-setup and Node 24.15.0, as Cornerstone3D does.
- Add publish.yml and scripts/release/*, adapted from the Cornerstone3D release
  flow: version from the last commit message, atomic push of the version commit
  and tag, then npm publish --provenance with OIDC.
- Fix repository url/directory in each package.json so provenance matches.
- Align healthlakestore to 1.7.6 with the other packages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Take #136 (squash of the branch this one builds on). Keep the pnpm side of the
bun-to-pnpm conflicts: drop bun.lock and the bunfig files, keep the pnpm audit step.
Carry over the review fixes: the scp bin scripts become 100755, and the braces
ignore comment in pnpm-workspace.yaml says the advisory is unreachable, not dev-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ruleset can allow it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mark @radicalimaging/healthlakestore private so the release scripts skip it,
and restore its version to 1.6.5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge Dockerfile and arm-Dockerfile into one Dockerfile that builds for
linux/amd64 and linux/arm64. arm64 compiles canvas from source, because
canvas 3.1.0 has no linux-arm64 prebuilt binary. The image build skips
s3-deploy, static-wado-deploy and healthlakestore.

Add the "Publish Docker images" workflow (workflow_dispatch only). It
builds a release tag on native amd64 and arm64 runners, pushes by digest
to GHCR, and joins the digests into one multi-arch tag. The publish job
uses the docker-hub environment, which holds the Docker Hub token behind
a required reviewer. Without the token, the job publishes to GHCR only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from chore/pnpm-12 to master October 8, 2026 18:53
wayfarer3130 and others added 5 commits October 8, 2026 15:07
Take master's tree (the squash merge of #137 and the v1.7.7 release) and keep only the Docker changes of this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…subject

The build job runs canvas and the CLI commands in each image before the
publish job tags it. The workflow header notes that the environment
changes the OIDC subject for a later move to Docker Hub OIDC. The
Dockerfile says why amd64 gets no canvas build packages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…blish workflow

The installer stage ran `npm install` on the packed packages, so npm
ignored pnpm-lock.yaml and the overrides of pnpm-workspace.yaml. The
1.7.7 image held 21 package versions that the lockfile does not have,
among them adm-zip 0.5.17 and 0.5.18 (5 high advisories) and dcmjs
0.38.3. `pnpm deploy --prod` of static-wado-webserver now installs the
image from the lockfile; the image holds no version outside it.

Dockerfile:
- Pin node:24-trixie and oven/bun:1.3.13 by digest. Both are trixie, so
  the canvas that arm64 compiles finds the same libraries at run time.
- Drop the installer stage and the root *.tgz copies.

docker-publish.yml:
- Fail when DOCKERHUB_TOKEN is missing, unless the ghcr_only input is set.
- Fail a run that does not start on master, instead of a green skip.
- Choose and check the tag in scripts/release/docker-release.mjs: master
  must contain it, and it must carry its version. Build the commit SHA.
- Pin actions by commit, restore no build cache, use permissions: {}.
- Keep the digests 30 days (the approval wait), with overwrite: true.
- Smoke test that the default command serves /dicomweb/studies.
- Describe the token scope and the GHCR limit of the gate correctly.

Keep platform: linux/amd64 in docker-compose.yml until arm64 images
exist, and say in the README which releases are multi-arch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… amd64 pin

A maintainer pushed 1.7.7 and latest as one linux/amd64 and linux/arm64
index, built from this branch. The README now says that the images are
multi-arch from 1.7.7, and docker-compose.yml no longer forces
platform: linux/amd64.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… only what the image needs

Review round 2 of the Docker publish workflow.

docker-publish.yml:
- Fail when a registry already holds X.Y.Z, unless the new overwrite
  input is set. Only "not found" counts as a free tag.
- The publish job has only packages: write.
- Explain why the token check runs after the approval, the artifact
  retention, and what the concurrency group cancels.

Dockerfile:
- Pin the dockerfile:1.7-labs frontend by digest.
- Build with the filter static-wado-webserver..., the packages that
  pnpm deploy copies (6 of 11 workspace projects).
- Use --no-install-recommends, and install ca-certificates explicitly.
- Keep the install scripts of the global pnpm: with --ignore-scripts,
  pnpm runs its Node launcher, and the arm64 canvas build fails with
  "node-gyp: not found".

docker-release.mjs uses only the release tags that master contains, and
quotes the input in its error messages.

The README says that only Docker Hub has the approval gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wayfarer3130
wayfarer3130 marked this pull request as ready for review October 8, 2026 22:47
… the image in PRs

Review round 3 of the Docker publish workflow.

docker-publish.yml:
- A version keeps one image. When GHCR holds the version (a run that
  stopped before Docker Hub, or a ghcr_only run), publish that image, not
  the new build. A tag with the same manifests counts as done, so a re-run
  continues; a different image needs overwrite. Check each new tag,
  latest included, against the expected manifests.
- Pin the BuildKit daemon of the build job by digest; the publish job runs
  no BuildKit daemon.
- Add the OCI labels source, revision and version.

docker-release.mjs refuses a tag whose Dockerfile does not install from
pnpm-lock.yaml (v1.7.7 and older), and resolves refs/tags/<tag>.

scripts/docker-smoke-test.sh holds the smoke test of both workflows. It
runs createdicomweb --help and mkdicomweb --help, uses a free host port,
and waits up to 180 s, for arm64 under QEMU.

New docker-ci.yml builds and tests the amd64 image in a pull request that
changes the Docker files or the lockfile.

Dockerfile: correct the canvas comment (node:24-trixie has the canvas
build libraries, so a failed amd64 download compiles canvas), and check
in the final stage that canvas loads.

docker-compose.yml drops port 11115: no image since at least 1.7.6 has
the DIMSE SCP of the old v1 tag. The docker:build scripts no longer run
cleanTgz.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wayfarer3130
wayfarer3130 requested a review from rleisti October 9, 2026 00:44
wayfarer3130 and others added 3 commits October 8, 2026 21:12
Review round 4 of the Docker publish workflow.

- Move the publish logic to scripts/docker-publish-tags.sh, with
  scripts/docker-publish-tags.test.sh: 13 checks against a local
  registry, in docker-ci.yml with shellcheck. A copy of the script
  without the revision check and the latest guard fails exactly those
  checks.
- latest moves only forward: a re-run of an older release leaves a
  newer latest alone.
- A GHCR version that the run reuses must carry the release commit as
  its revision label, because GHCR is not behind the approval.
- Refuse ghcr_only with overwrite.
- The Dockerfile replaces the labels of oven/bun with the labels of this
  project; the workflow passes version, revision and the commit time,
  and the tag script adds index annotations.
- The final stage runs apt-get upgrade: the pinned base image predates
  the trixie security updates (docker scout: 5 critical and 16 high
  findings before, 0 critical and 3 unfixed high after).
- docker-ci.yml builds amd64 and arm64, and also starts for package
  changes.
- The smoke test sets its trap first, limits each request to 5 s, and
  takes SMOKE_TIMEOUT_SECONDS for QEMU runs.
- The Dockerfile marker must be on a RUN line; publish runs on
  ubuntu-24.04; the README drops the DIMSE port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ten the tag test

Review round 5 of the Docker publish workflow.

- The test disables SC2317 and SC2329: the shellcheck 0.9.0 of the
  ubuntu-24.04 runner uses the older code, so the CI job failed.
- docker-publish-tags.sh trusts only an X.Y.Z version label on latest.
  sort -V puts `dev` (the label of a local build) above every number, so
  one local push to latest would have kept latest there for good.
- The local docker scripts use static-dicomweb:dev, not the Docker Hub
  name.
- docker-publish-tags.sh uses inherit_errexit, and refuses a digest file
  name that is not 64 hex characters.
- The test now has 18 checks: Docker Hub stays free after ghcr_only,
  1.8.2 has its own images, latest is checked after each move, a dev
  label, and a bad digest name. The registry binds 127.0.0.1, and the
  cleanup removes its volume.
- The publish job gets contents: read for its checkout.
- docker-ci.yml also starts for tsconfig.json, tsconfig.base.json and
  babel.config.js.
- startStaticDicomweb.sh runs only monitordicomwebserver: the image has
  no dicomwebscp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…script executable

Review round 6 of the Docker publish workflow.

- docker-publish-tags.sh no longer takes LATEST. Each run moves latest,
  unless the version label of the current latest is a newer X.Y.Z. Before,
  only the newest tag on master could move latest, so an older release
  could not move latest forward when a newer tag had no image, and an
  overwrite run left latest on the replaced image. docker-release.mjs no
  longer writes a latest output.
- git marks docker/startStaticDicomweb.sh executable, so an image built
  on Linux can run it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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