# 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 # bit lighter and more secure. When building the backend, we always # copy in package.json and package-lock.json first, as a distinct layer, so that # Docker's cache will let us skip installs when the dependencies haven't changed. # We build on debian because it has fewer dependency issues than Alpine for our # native modules, and we don't really care about the larger image size. FROM node:24.14.1-bullseye-slim AS server_base WORKDIR /app RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/* COPY ["server/package.json", "server/package-lock.json", "./"] RUN npm ci COPY ["server", "./"] FROM server_base AS build_backend 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 RUN apt-get update && apt-get install dumb-init COPY --from=build_backend ["/app/package.json", "/app/package-lock.json", "./"] RUN npm ci --omit=dev COPY --from=build_backend /app/transpiled ./ # See https://github.com/Yelp/dumb-init ENTRYPOINT ["/usr/bin/dumb-init", "--"] # ARG is used to get the release id into the ENV from the command line, and then # the ENV command exposes the release id to the API app at runtime for logging. # We put this after npm installs so it doesn't invalidate cache of prior steps, # as it always changes. ARG BUILD_ID 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 EXPOSE 8080 CMD ["node", "bin/www.js"] FROM backend_base AS build_worker_runner CMD ["node", "bin/run-worker-or-job.js"]