feat(modules): mount the modules directory as a volume (phase 2, PR 9) #136
Reference in New Issue
Block a user
No description provided.
Delete Branch "feature/module-compose-volume"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes Phase 2. Last of the nine scaffolding PRs, and the one that makes the WordPress model real: modules live on a mount, never in the image, so an operator adds one to a pull-only deployment without building anything.
Docs half: RunicGateway/docs#134.
What changed
docker-compose.yml./modules→/app/modules, read-write, plusMODULES_DIRDockerfile/app/modulesowned bynode.dockerignoremodules.gitignoremodules/*except its READMEmodules/README.mdREADME.mdserver/scripts/routeManifest.jsNo application code. 933 server + 160 client tests pass unchanged,
routes.manifest.jsonstays at 230 routes, OpenAPI regenerates byte-identical.A bind mount, not a named volume
§2.5 said "the same treatment
uploadsalready gets", reaching for a named volume by analogy. The build settled it the other way: placing a module directory by hand is a supported install, and a named volume makes that adocker cpinto a running container — the least discoverable mechanism Docker offers, for the one install path an operator without the admin panel has. A bind mount makes ittar -xf … -C ./modules, and makes the installed set something an operator can see. Read-write, because §2.5's install unpacks into it from inside the container.Two silent failures this closes
The directory is tracked (via its README — the shape
brand/already uses). Docker recreates a missing bind-mount source asroot:root, and the container runs as uid 1000. Deletemodules/from a checkout and the next install fails on a permission error that names no cause..dockerignoreexcludes it.COPY . .would otherwise bake whatever module the builder had checked out into every image — and Docker seeds a fresh named volume from image contents, so it could have surfaced on a production deployment that never installed it. This exclusion is what makes "never in the image" true rather than merely intended.Verified against a running container
A compose file that parses proves nothing about ownership, and nothing about what the image contains — so this was checked on the real thing (
-f docker-compose.dev.yml, projectrgmodvol):/app/modulesdespite a module sitting in the build contextonBoot, reachesstarted;moduleRootis/app/modules/volsmokeGET /api/v1/public/moduleslists it; its API route answersserver/index.jsandmodule.jsonare both404— withCache-Control: no-cacheand the right MIME;/modules/<unknown>is404 application/json, not the SPA shell§7.7 browser smoke, re-run against the containerised stack rather than a working tree — in Chrome, console open:
PublicLayout, drawing React and the UI kit offwindow.__rgscript-src 'self', zero CSP reports, zero console errorsNegative case: removing the directory by hand and restarting reconciles the row to
startup_failed/ stagerequire("module directory not present on the volume") exactly as §2.4 says, and leaves core healthy —{"modules":[]}, no script injected,/api/healthok.One unrelated correction
scripts/routeManifest.jsenumerated the filesystem-conditional mounts it excludes and had never been told about/moduleswhen PR 7 added it. Its filter is an allowlist, so the behaviour was always right and only the explanation was stale — comment and README prose only.🤖 Generated with Claude Code
https://claude.ai/code/session_018ocYxQWk3EhZe5gWRJXFU8