We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.

This lesson's interactive features are locked, please to keep using them

Build Artifacts and Deployment Hygiene

A production container (like a Docker container) should have access to what the app needs to run and not much else. Extra tools and files increase image size and give an attacker more to work with.

Imagine a media app that runs TypeScript directly using Node.js 24. Its Dockerfile might look like this:

FROM node:24-alpine

WORKDIR /app
COPY . .
RUN npm ci

CMD ["npm", "start"]

It copies the entire build context, installs development tooling in the final image, and runs as the default root user. Not great.

A multi-stage build can prepare production dependencies separately, then copy only the required runtime files into the final stage:

FROM node:24-alpine AS dependencies
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM node:24-alpine AS runtime
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY package.json ./
COPY src ./src
COPY public ./public
RUN mkdir -p data/uploads data/render-jobs \
  && chown -R node:node data
COPY --chown=node:node data/uploads/sample-poster.webp ./data/uploads/
USER node
CMD ["node", "src/main.ts"]

The exact steps depend on the app, of course. But we shouldn't dump the entire project into production. Explicitly copy only the stuff production needs. The final image is smaller and doesn't include development tools an attacker could use.

Running as a non-root user (node, in this case) limits what a compromised process can do. Of course, the app's user still needs permission to perform its ordinary runtime work.

Assignment

Bearly Secure's production image currently includes the entire project, development tools, and a root process. Replace it with a narrow, multi-stage Node.js 24 image.

Run and submit the CLI tests from the project root.