0 / 11 lessons — 0%
Lesson 05 / 11

Writing a Dockerfile

A Dockerfile is a recipe: each instruction bakes one more layer onto the image. Layers are cached, so order them from "changes rarely" (installing dependencies) to "changes constantly" (your source code) — that way a code change doesn't force a slow dependency reinstall on every build.

# syntax=docker/dockerfile:1 FROM node:20-slim AS build WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:20-slim WORKDIR /app ENV NODE_ENV=production COPY --from=build /app/dist ./dist COPY --from=build /app/node_modules ./node_modules EXPOSE 3000 USER node CMD ["node", "dist/server.js"]

This is a multi-stage build — the build stage keeps all the heavy tooling, but the final image only copies over the finished output. Smaller image, fewer things for an attacker to exploit.

FROM node:20-slim — base layer COPY package*.json / RUN npm ci COPY . . RUN npm run build container's writable layer — exists only while it runs cached rebuilds often
Layers stack bottom-up and are cached. Only what's above the first changed instruction gets rebuilt.
InstructionPurpose
FROMbase image to start from
WORKDIRsets the working directory for what follows
COPYcopy files from build context into the image
RUNexecute a command, bake the result into a layer
CMDdefault command when the container starts
ENTRYPOINTfixed executable; CMD becomes its default args
EXPOSEdocuments the port the app listens on
docker build -t myapp:1.0 . docker run -p 3000:3000 myapp:1.0
Try it yourselfAdd a .dockerignore file next to your Dockerfile with node_modules and .git in it, rebuild, and watch the build get noticeably faster — you just stopped Docker from copying gigabytes it never needed.