# syntax=docker/dockerfile:1 ############################################################################### # SHARP-ER web - React Router 8 (framework mode, SSR) on Node 22 # # Adapted from the React Router template Dockerfile. Two things changed: # * npm -> pnpm, because this repository is a pnpm workspace and there is no # package-lock.json to `npm ci` from; # * the build context is the REPOSITORY ROOT, where pnpm-lock.yaml, # pnpm-workspace.yaml and .npmrc live. # # docker build -f apps/web/Dockerfile -t sharper-web . # docker compose build web # does exactly that # # The server reads STRAPI_URL / STRAPI_TOKEN / SITE_URL at RUN time, never at # build time, so one image is valid for every environment. ############################################################################### # --- base: the toolchain both stages share ---------------------------------- FROM node:22-bookworm-slim AS base ENV PNPM_VERSION=9.12.3 RUN apt-get update \ && apt-get install -y --no-install-recommends ca-certificates \ && rm -rf /var/lib/apt/lists/* \ && npm install --global "pnpm@${PNPM_VERSION}" WORKDIR /repo # --- build: install dependencies and produce build/client + build/server ----- FROM base AS build # Manifests first so the install layer stays cached. Both workspace manifests # are copied: pnpm checks the lockfile against every project in the workspace # and treats it as stale if one is missing. COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./ COPY apps/web/package.json ./apps/web/ COPY apps/cms/package.json ./apps/cms/ # `--filter web...` keeps Strapi out of this image entirely. # devDependencies are required: vite, @react-router/dev and tailwind build here. RUN pnpm install --frozen-lockfile --filter web... COPY apps/web ./apps/web RUN pnpm --filter web build # --- runtime ---------------------------------------------------------------- FROM base AS runtime ENV NODE_ENV=production \ PORT=3000 COPY --from=build /repo/node_modules ./node_modules COPY --from=build /repo/package.json /repo/pnpm-workspace.yaml /repo/.npmrc ./ COPY --from=build /repo/apps/web ./apps/web RUN chown -R node:node /repo/apps/web USER node WORKDIR /repo/apps/web EXPOSE 3000 # /en is a real page: it proves the SSR handler renders, not just that a socket # is open. It stays 200 even when the CMS is unreachable, because every route # falls back to its bundled draft copy - so this healthcheck tracks the web # service alone. HEALTHCHECK --interval=15s --timeout=5s --start-period=30s --retries=5 \ CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/en').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" # react-router-serve, the production server that ships with the framework. CMD ["pnpm", "start"]