feat: add multi-arch Docker image build to release pipeline#882
Conversation
Build and push linux/amd64 and linux/arm64 Docker images to DockerHub (systeminit/swamp) on each release, using pre-compiled binaries from the GitHub Release. Based on denoland/deno for Deno runtime support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
No blocking issues found. This PR adds Docker image building to the release pipeline with a minimal Dockerfile. No TypeScript source changes, so CLAUDE.md conventions around tests, license headers, and libswamp imports are not applicable.
Suggestions
-
Pin the Deno base image version in the Dockerfile —
FROM denoland/deno:latestmeans builds are not reproducible across time. Consider pinning to a specific version (e.g.,denoland/deno:2.2.2) or at least a major version tag, and updating it deliberately. -
Pass the version from the
releasejob via outputs instead of usinggh release view— While theconcurrencygroup prevents parallel workflow runs,gh release viewreturns the repo's latest release, which could theoretically be a manually-created release. Passing the version/tag as a job output fromreleasetodockerwould be more robust:# In the release job: outputs: version: ${{ steps.version.outputs.version }} # In the docker job, reference: needs.release.outputs.version
-
Consider adding a
latesttag — Many Docker users expect alatesttag alongside versioned tags. Could be added to the manifest creation step. -
Consider running as non-root — The Dockerfile runs as root by default. Adding a
USERdirective would follow container security best practices, though this depends on whether swamp needs root-level access at runtime. -
RUN chmod +xis redundant — The workflow already runschmod +xon the binary before the Docker build context is created (line 192). TheCOPYinstruction preserves file permissions, so theRUN chmod +xin the Dockerfile is unnecessary (though harmless).
There was a problem hiding this comment.
CI Security Review
Critical / High
None.
Medium
-
Unpinned Docker actions (release.yml:196-227):
docker/setup-qemu-action@v3,docker/setup-buildx-action@v3,docker/login-action@v3, anddocker/build-push-action@v6are pinned to tags, not commit SHAs. Notablydocker/login-actionhandles Docker Hub credentials — a compromised tag could exfiltrateDOCKERHUB_TOKEN. However, this is consistent with the existing repo pattern (e.g.,softprops/action-gh-release@v2,peter-evans/repository-dispatch@v3), so flagging as advisory only. Fix: Pin to full commit SHAs. -
Unpinned base image in Dockerfile (Dockerfile:1):
FROM denoland/deno:latestmeans every build pulls whatever is currently taggedlatest. A compromised or breaking upstream image affects all future builds. Fix: Pin to a specific version or digest, e.g.,FROM denoland/deno:2.2.2or use a@sha256:...digest.
Low
${{ env.docker_tag }}interpolated inrun:block (release.yml:231-234): Thedocker_tagvalue is derived from a tag this workflow created (date-based format), so exploitation risk is negligible. Using shell variable expansion ("${docker_tag}") instead of Actions expression syntax inrun:blocks would be marginally safer as a general practice.
Verdict
PASS — No critical or high severity findings. The new docker job is well-structured: it uses needs: release to sequence correctly, narrows permissions to contents: read at job level, checks out ref: main (not PR head), and secrets are scoped appropriately. The medium findings are supply-chain hygiene improvements worth addressing but do not block merge.
Pin the Dockerfile base image to denoland/deno:2.7.5 instead of :latest for reproducible builds. Replace gh release view in the docker job with an explicit job output from the release job, eliminating the possibility of picking up a different release tag. Addresses review feedback from #882. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…883) ## Summary - **Pin Dockerfile base image** to `denoland/deno:2.7.5` instead of `:latest` for reproducible, deterministic builds - **Pass version via job outputs** from the `release` job to the `docker` job instead of querying `gh release view`, which could theoretically return a different release ## Impact These changes make the release pipeline more deterministic: 1. **Reproducible Docker builds** — Pinning the Deno base image means the same Dockerfile produces the same image regardless of when it's built. Previously, `:latest` meant builds could silently pick up a new Deno version with breaking changes or security issues. 2. **Correct version propagation** — The docker job now receives the exact version from the release job that created it via `needs.release.outputs.version`. Previously, `gh release view` returned the repo's latest release, which could be wrong if a manual release was created between jobs. ## Why this is correct - The `release` job already computes the version in the `version` step — we simply expose it as a job output - The `docker` job already declares `needs: release`, so the output is guaranteed to be available - No behavioral change in the happy path; this only eliminates edge-case failure modes Addresses review feedback from #882. ## Test plan - [ ] Verify CI passes on this PR - [ ] Confirm release workflow still builds and pushes Docker images correctly on next merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Build and push linux/amd64 and linux/arm64 Docker images to DockerHub
(systeminit/swamp) on each release, using pre-compiled binaries from
the GitHub Release. Based on denoland/deno for Deno runtime support.
Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com