# syntax=docker/dockerfile:1 # The Memby operations console. # # Two stages: Node builds the static bundle, nginx serves it. Nothing from Node survives # into the running image — the console is a directory of files, and the container's only # job is to hand them over on the compose network. It is never published: the gateway # reverse-proxies /admin to it, so the console shares the gateway's origin, cookie and # single ingress. FROM node:22-alpine AS build WORKDIR /ui # Dependencies first so an edit to the source does not re-resolve the whole tree. COPY package.json package-lock.json* ./ # `npm ci` when there is a lockfile — reproducible, and the only reason a lockfile is # checked in — falling back to `npm install` so a first build works without one. RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi COPY tsconfig.json vite.config.ts index.html ./ COPY src ./src # The type check is part of the build rather than a separate step: a console that compiles # to JavaScript but reads a field the gateway does not send is exactly the failure the # hand-written wire types exist to catch, and it must not reach a deployment. RUN npm run build FROM nginx:1.27-alpine # Read-only, non-root and no default site: the image serves one directory and does nothing # else. nginx's own temp paths are the only writable thing it needs, and they are declared # as tmpfs in docker-compose.yml. COPY nginx.conf /etc/nginx/conf.d/default.conf COPY --from=build /ui/dist /usr/share/nginx/html EXPOSE 80