Skip to content

build(afm): run dev/CI containers as a non-root user#44

Merged
P4suta merged 1 commit into
mainfrom
build/afm-nonroot-dev-container
May 30, 2026
Merged

build(afm): run dev/CI containers as a non-root user#44
P4suta merged 1 commit into
mainfrom
build/afm-nonroot-dev-container

Conversation

@P4suta

@P4suta P4suta commented May 30, 2026

Copy link
Copy Markdown
Owner

What

The dev image ran as root, so every file a container wrote into the
/workspace bind mount (generated artefacts, wasm pkg/, mdbook output,
node_modules) landed root-owned on the host — the classic bind-mount
footgun, and not how a dev container should behave.

This switches the dev/CI containers to a non-root user matched to the host
UID
, the industry-standard pattern.

How

  • Dockerfile (dev + book stages): create a non-root dev user
    (UID/GID default 1000, overridable via --build-arg UID=/GID=),
    chown the /cargo cache dirs and the playground node_modules
    mountpoint so fresh named volumes initialise dev-owned, and USER dev
    at the end of each stage. The fuzz stage flips back to USER root for
    its /usr/local installs (nightly + cargo-fuzz/udeps), then drops to
    dev again.
  • docker-compose.yml: user: "${AFM_UID:-1000}:${AFM_GID:-1000}" on the
    dev/fuzz/ci/book/playground services. Unset locally → the
    host-matched 1000 (host-owned writes); CI sets AFM_UID=0 → root.
  • setup-dev-image action + book CI job: emit AFM_UID=0/AFM_GID=0
    so CI stays byte-identical to the proven root behaviour — the
    ephemeral runner's checkout is owned by a different UID and a non-root
    container can't create files (pkg/, book/, dist/) inside it.

Rationale for keeping CI as root

CI is ephemeral and ownership is throwaway there; the runner's checkout is
owned by a different UID than any baked image user, so root is the
simplest correct choice and avoids a cross-UID write-permission class of
failure. Local dev — where the bind mount is the developer's real tree — is
where non-root matters, and that's now fixed.

Verification (local, UID 1000)

  • docker compose run --rm dev iduid=1000(dev); AFM_UID=0 … idroot.
  • A container-written bind-mount file is yasunobu:yasunobu-owned (was root).
  • Pre-existing root-owned cargo volumes migrated in place (chown -R 1000 /cargo, no cache loss); fresh playground-node-modules volume initialises dev-owned.
  • just ci green non-root (pre-push gate); just book-build writes host-owned book/.

🤖 Generated with Claude Code

The dev image ran as root, so every file a container wrote into the
/workspace bind mount (generated artefacts, wasm pkg/, mdbook output,
node_modules) landed root-owned on the host — the classic bind-mount
footgun, and not how a dev container should behave.

Create a non-root `dev` user (UID/GID default 1000, overridable via
`--build-arg UID=/GID=`) in the dev and book image stages, chown the
/cargo cache dirs and the playground node_modules mountpoint so fresh
named volumes initialise dev-owned, and `USER dev` at the end of each
stage (the fuzz stage flips back to root for its /usr/local installs,
then drops to dev again).

Runtime UID is selected by `user: "${AFM_UID:-1000}:${AFM_GID:-1000}"`
on the dev/fuzz/ci/book/playground services: unset locally → the
host-matched 1000 (host-owned writes); CI sets AFM_UID=0 (setup-dev-image
action + the book job env) → root, because the ephemeral runner's
checkout is owned by a different UID and a non-root container cannot
create files inside it. CI thus stays byte-identical to the proven
root behaviour while local dev becomes non-root.

Pre-existing root-owned cargo named volumes migrate in place with a
one-off `docker compose run --user 0:0 dev chown -R 1000:1000 /cargo`
(no cache loss); fresh volumes need none.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@P4suta P4suta enabled auto-merge (squash) May 30, 2026 21:55
@P4suta P4suta merged commit fa7f1d2 into main May 30, 2026
22 checks passed
@P4suta P4suta deleted the build/afm-nonroot-dev-container branch May 30, 2026 21:59
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.

1 participant