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.
Layers stack bottom-up and are cached. Only what's above the first changed instruction gets rebuilt.
| Instruction | Purpose |
|---|---|
FROM | base image to start from |
WORKDIR | sets the working directory for what follows |
COPY | copy files from build context into the image |
RUN | execute a command, bake the result into a layer |
CMD | default command when the container starts |
ENTRYPOINT | fixed executable; CMD becomes its default args |
EXPOSE | documents 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.