chore(site): migrate scorecrux.com preview image to Chainguard nginx - #28
Merged
Conversation
ExecPlan chainguard-image-migration, M2 (frontdoor / web-facing). site/Dockerfile: FROM nginx:alpine -> cgr.dev/chainguard/nginx:latest, and EXPOSE 80 -> EXPOSE 8080. Why the port moves: upstream nginx:alpine runs its master as root and its bundled server block listens on 80. The Chainguard image runs as uid 65532, which cannot bind a privileged port, so its /etc/nginx/conf.d/nginx.default.conf listens on 8080 instead. The choice is either to keep port 80 by shipping a custom config plus a UID change (giving back the non-root posture that is the point of the migration), or to follow the base and move the published port. This takes the second option; nothing in this repo or in a sibling compose/proxy consumes the image, so the only consumer is the documented local run command, updated in the header comment from `-p 8888:80` to `-p 8888:8080`. What deliberately did not change: the document root is /usr/share/nginx/html on both bases, so the COPY is untouched and no custom nginx config is needed. The base sets ENTRYPOINT ["/usr/sbin/nginx"] with a CMD carrying the config flags; both are left alone rather than overridden, since an added CMD would append to that entrypoint. The runtime has no shell, so any future addition here must be exec form. Supply-chain note: no digest pin was dropped -- the previous base was the floating tag `nginx:alpine`, already unpinned. Free-tier Chainguard is :latest-only, so the new base cannot be pinned either (plan policy, 2026-04-10). Verified locally: `docker build -f site/Dockerfile -t cg-test-scorecrux-site:probe site/` exit 0; container on -p 8091:8080 returned HTTP 200 for `/` with a body byte-identical to site/index.html (26485 bytes) and 404 for an unknown path; nginx 1.31.4, only the benign "user directive ignored" warning from the base's own nginx.conf. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ExecPlan
chainguard-image-migration— M2 (frontdoor / web-facing), ScoreCrux slice.Merging this does NOT deploy to production
Checked before opening:
.github/workflows/holds exactly two workflows, and neither builds or ships a container..github/workflows/ci.yml—push: branches: [main]+pull_request, but the job is onlypnpm install / typecheck / test:coverage / buildon Node 20 and 22. No Docker step, no path filter onsite/**..github/workflows/publish.yml— triggers onrelease: [created]only, and runsnpm publishfor the npm package. Not reachable by a merge tomain.site/Dockerfilehas no consumer beyond a human running it locally — no compose file, no deploy script, and nothing inScoreCrux-Frontdoor/orInfraCrux/referencessite/Dockerfileor ascorecrux-siteimage (grep -rn 'scorecrux/site\|scorecrux-site\|site/Dockerfile'over both returns nothing). The image was added in bdee1dd purely as a local preview of scorecrux.com.What changed
site/Dockerfile(the only file touched)nginx:alpinecgr.dev/chainguard/nginx:latestEXPOSE808080COPYindex.html -> /usr/share/nginx/html/index.htmlPlus a header comment recording the mechanism and the corrected run command.
Why the port moves. Upstream
nginx:alpineruns its master process as root, so its bundled server block can bind port 80. The Chainguard image runs as uid 65532 (Config.User: "65532"on the pulled image), which cannot bind a privileged port, so the config it ships listens on 8080:The alternative was keeping port 80 by shipping a custom config and switching back to a root UID — which hands back the non-root posture that is the point of the migration. Since the only consumer is the run command documented in bdee1dd's commit message, this follows the base instead. The local run command changes from
-p 8888:80to-p 8888:8080, and that is now written into the Dockerfile header so it does not have to be rediscovered.What deliberately did not change. The document root is
/usr/share/nginx/htmlon both bases, so theCOPYline is untouched and no custom nginx config is needed. The base setsENTRYPOINT ["/usr/sbin/nginx"]withCMD ["-c","/etc/nginx/nginx.conf","-e","/dev/stderr","-g","daemon off;"]; both are left alone rather than overridden, because an addedCMDwould append to that entrypoint. The runtime has no shell, so anything added here later must be exec form. NoRUN, no shell-form directive, and no named volume is involved, so the uid-65532 write-permission trap does not apply — the copied file isroot:root 0644, world-readable, and nginx serves it fine as 65532.Digest pins dropped
None. The previous base was the floating tag
nginx:alpine— already unpinned, so there is no digest to give up here. For the record, the new base is also unpinnable: free-tier Chainguard is:latest-only (plan policy, decided 2026-04-10). The digest resolved at build time wassha256:f6cbe96998972d87ebe30952ec1b6f3cff4103c33c889ffca7e13053e2036571(image created 2026-08-20).Build + smoke evidence (actually run, WSL2, Docker 29.4.3)
docker logs cg-scorecrux-site:The single
[warn]is the base's ownnginx.confcarrying auser nginx;directive that a non-root master ignores; it is emitted by the unmodified Chainguard image and is not caused by this change. Container statusUp, ports0.0.0.0:8091->8080/tcp. Resulting image is 26.3MB. Probe container and both images (cg-test-scorecrux-site:probe, the pulled base) were removed afterwards; nodocker system prunewas run.Not done here
Production rollout stays in the plan's M8 — and in this repo's case there is no container rollout to do, since nothing deploys this image.
🤖 Generated with Claude Code