Closes Phase 2. Modules live on a mount, never in the image — that is what lets an operator add one to a pull-only deployment without building anything. `./modules` is a bind mount rather than a named volume: placing a module directory by hand is a supported install (MODULE_SYSTEM.md §2.5), and that has to be doable from the host rather than through `docker cp`. Read-write, because the admin panel's install/uninstall unpacks and removes directories there. The directory is tracked via its README so it exists in the checkout with the operator's own ownership — Docker recreates a missing bind-mount source as root:root, which the container user could not then write. `.dockerignore` excludes it so a module in the builder's working tree can never ship inside an image. Also corrects the route-manifest generator's list of filesystem-conditional mounts, which never picked up `/modules` when PR 7 added it. Comment only; the generator filters on an allowlist, so its behaviour was already right. Verified against a real container, not just a parsed compose file: image carries an empty node-owned /app/modules despite a module in the build context; a module on the bind mount loads, mounts, replays and reaches `started`; `/api/v1/public/modules` lists it; the chunk serves from the entry's directory only (server source and module.json 404) with `no-cache`; the injected tag follows core's bundle; and in Chrome the page renders on first paint inside core's PublicLayout with its nav row interleaved into core's public nav, under enforced `script-src 'self'` with zero CSP reports and no console errors. Removing the directory by hand reconciles the row to `startup_failed`/`require` and leaves core healthy with no injection. 933 server + 160 client tests pass, manifest unchanged at 230 routes, swagger regenerates byte-identical. Co-Authored-By: Claude <noreply@anthropic.com>
35 lines
1.2 KiB
Docker
35 lines
1.2 KiB
Docker
FROM node:20-alpine
|
|
|
|
WORKDIR /app
|
|
|
|
# Install server deps first for better layer caching (production only).
|
|
COPY server/package*.json server/
|
|
RUN npm install --prefix server --omit=dev
|
|
|
|
# Copy the rest of the repo. .dockerignore keeps node_modules/.env/_reference out,
|
|
# so the server deps installed above are preserved.
|
|
COPY . .
|
|
|
|
# Build the client if it is present (added in the frontend phase). Until then the
|
|
# server runs API-only and serves a placeholder at /.
|
|
RUN if [ -f client/package.json ]; then \
|
|
npm install --prefix client && npm run build --prefix client; \
|
|
else \
|
|
echo "No client/ present — building server-only image"; \
|
|
fi
|
|
|
|
# Persistent uploads + logs live on mounted volumes.
|
|
RUN mkdir -p /app/uploads /app/logs && chown -R node:node /app/uploads /app/logs
|
|
|
|
# Installed modules are mounted in too (docker-compose.yml), and .dockerignore
|
|
# keeps any local modules/ OUT of the image — a module must never be baked in.
|
|
# The directory is still created here so a container run without the mount finds
|
|
# an empty, writable modules dir rather than no directory at all.
|
|
RUN mkdir -p /app/modules && chown node:node /app/modules
|
|
|
|
USER node
|
|
|
|
EXPOSE 3000
|
|
|
|
CMD ["npm", "start", "--prefix", "server"]
|