fix(deploy): restore read-only-rootfs tmpfs ownership — podman 5.6.2 rejects tmpfs uid= (unblocks #333 deploy)#337
Merged
Conversation
…ejected tmpfs uid= (#333 deploy fix) The droplet's podman 5.6.2 rejects `--tmpfs=...:uid=1001,gid=1001` outright (`Error: unknown mount option "uid=1001": invalid mount option`; and there is no working `--mount type=tmpfs,tmpfs-uid=` either). This fail-closed #333's pre-cutover BOOT_CHECK gate on the first push-to-main deploy. build+test were green because nothing there runs podman -- and test_tmpfs_options_pinned even pinned the rejected string, so the test agreed with the workflow but not with reality. The gate aborted before cutover, so prod kept serving the pre-#333 image (no outage; the gate did its job). Restore uid1001 ownership the way podman 5.6.2 supports: - mount /home/fingpt and /app/staticfiles at mode=0755 (owner-writable once chowned, not the tmpfs-default world-writable 1777), on both the gate and the ExecStart podman run (flag parity preserved); - chown both dirs to fingpt in entrypoint.sh root-init, before the setpriv drop, while PID1 still holds CAP_CHOWN. Non-recursive is sufficient: only the tmpfs mount point comes up root-owned; tmpcopyup content (baked ~/.cache/fontconfig) is already fingpt-owned. This is behaviorally identical to the intended-but- rejected uid=1001 tmpfs. Correct the invariant that hid the bug: test_tmpfs_options_pinned now asserts no uid=/gid= on the tmpfs (+ mode=0755), and a new test_entrypoint_chowns_read_only_tmpfs_dirs pins the root-init chown ordering. Verified on the droplet against the real #333 image: podman accepts the new flags, root-init chown yields `drwxr-xr-x 1001 1001`, and a setpriv-dropped uid1001 writes both dirs successfully. Co-Authored-By: Claude Opus 4.8 (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.
Why
The push-to-main deploy for #333 (read-only rootfs + full-boot pre-cutover gate) failed on the droplet — Actions run 28664065475.
build✅ andtest✅ passed; the failure was purely on-box:Root cause: the droplet's podman 5.6.2 rejects tmpfs
uid=/gid=mount options outright — both--tmpfs=…:uid=1001,gid=1001and--mount type=tmpfs,tmpfs-uid=…fail (verified live on the box). #333 introduced those options on/home/fingptand/app/staticfilesin both the pre-cutover gate and theExecStart.No outage: the gate fail-closed before cutover, so prod kept serving the pre-#333 image (verified:
fingpt-apiUp … (healthy)on the old digest, not the #333 digest). This is deterministic, not transient — a re-run fails identically.Why CI didn't catch it:
test_dockerfile_nonroot.test_tmpfs_options_pinnedasserted the literal stringuid=1001,gid=1001as the "correct" invariant — so the test and the workflow agreed with each other but not with podman, and nothing inbuild/testever runs podman.What
Restore uid1001 ownership the way podman 5.6.2 actually supports — chown inside the container instead of via a (rejected) tmpfs option:
entrypoint.sh— root-init nowchown fingpt:fingpt /home/fingpt /app/staticfiles, before thesetprivdrop, while PID1 still holdsCAP_CHOWN. Non-recursive is sufficient: only the tmpfs mount point comes up root-owned;tmpcopyupcontent (the baked~/.cache/fontconfig) is already fingpt-owned.backend-deploy.yml—uid=1001,gid=1001→mode=0755on both the gate andExecStarttmpfs flags (flag parity preserved).mode=0755lands the dirs owner-writable, not the tmpfs-default world-writable1777, once chowned.test_dockerfile_nonroot.py— correctedtest_tmpfs_options_pinnedto pin the real invariant (nouid=/gid=;mode=0755) and addedtest_entrypoint_chowns_read_only_tmpfs_dirs(asserts the root-init chown runs, beforesetpriv).Net effect is behaviorally identical to the intended-but-rejected
uid=1001tmpfs: 1001-owned mount point + fingpt-owned copied content.Verification
main, pass after the fix.test_dockerfile_nonroot+test_entrypoint_store_build= 28 passed; gate↔ExecStart parity test still green.9b7762a9…): podman acceptsmode=0755; root-init chown yieldsdrwxr-xr-x 1001 1001(owner-writable, not world-writable); asetpriv-dropped uid1001 writes both dirs; copied-up.cachestays fingpt-owned.backend-deploy.ymluses--read-only/--tmpfs).Deploy note
Both halves land atomically on merge to
main: the rebuild bakes the new entrypoint chown and the new workflow runs themode=0755gate in the same deploy. (Workflow-change-alone would clear the mount error but then fail the BOOT_CHECK$HOMEwritability probe on the root-owned tmpfs.) This unblocks #333's deploy.Fixes the deploy regression from #333.
🤖 Generated with Claude Code