diff --git a/Dockerfile b/Dockerfile index 9ee88eaa..6807a10d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# This is a fairly-standard multi-stage Dockerfile. We build +# This is a fairly-standard multi-stage Dockerfile. We build # backend in a Node image, and then we copy the built files # (but not the devDependencies [like typescript, etc.] or the raw source # files) to final images that we'll actually run. This makes the final image a @@ -18,6 +18,13 @@ COPY ["server", "./"] FROM server_base AS build_backend RUN npm run build +FROM node:24.14.1-bullseye-slim AS build_client +WORKDIR /app +COPY ["client/package.json", "client/package-lock.json", "./"] +RUN npm ci +COPY ["client", "./"] +RUN npm run build + # make a shared layer that can be the base for worker and api images. FROM node:24.14.1-bullseye-slim AS backend_base WORKDIR /app @@ -39,6 +46,7 @@ ENV BUILD_ID=$BUILD_ID # Expose 8080 because the backend will run on this port absent a # process.env.PORT to the contrary. FROM backend_base AS build_server +COPY --from=build_client /app/build ./client-build EXPOSE 8080 CMD ["node", "bin/www.js"] diff --git a/server/server.ts b/server/server.ts index 4c21d95f..7437a23a 100644 --- a/server/server.ts +++ b/server/server.ts @@ -1,3 +1,5 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; import express from 'express'; import makeApiApp from './api.js'; @@ -50,5 +52,17 @@ export default async function makeWWWServer(deps: Dependencies) { app.use('/api/v1', apiApp); + const clientBuildPath = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + 'client-build', + ); + app.use(express.static(clientBuildPath)); + app.get('{*path}', (_req, res, next) => { + const indexPath = path.join(clientBuildPath, 'index.html'); + res.sendFile(indexPath, (err) => { + if (err) next(); + }); + }); + return { app, shutdown }; }