Images and Dockerfiles
🏠The everyday picture
A recipe card for a standard meal box: start with this base, add these ingredients in this order, label it. Anyone following the card produces an identical box.
📘What it really is
- A Dockerfile is the recipe for an image. For your Node.js API:
# ---- build stage ----
FROM node:22-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ---- run stage ----
FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
USER node
EXPOSE 3001
CMD ["node", "dist/server.js"]
- Good practices shown here:
- official, slim base images, with a pinned version;
- multi-stage builds: build tools stay in the first stage; the final image contains only what's needed to run;
- copy
package*.jsonfirst and install, then copy the code: Docker caches layers, so dependencies are only reinstalled when they change, making rebuilds fast; - run as a non-root user (
USER node); - a
.dockerignorefile excludingnode_modules,.git,.envand test files, so they're never copied into the image. -
Never put secrets in an image (no
.env, no keys inENVlines). Anyone with the image can read them. Pass them at run time (Lesson 5). -
Build and run:
docker build -t crm-api:1.0 .
docker run --rm -p 127.0.0.1:3001:3001 --env-file .env crm-api:1.0
🖱️Try it
Write the Dockerfile and .dockerignore for your API. Build it, check its size with docker images, and run it. Then change
one line of code and rebuild, noticing which steps are cached. Try docker history crm-api:1.0 to see the layers.
✅Quick check
Why copy package*.json and install before copying the rest of the code?
Show the answer
Docker caches that layer, so dependencies are only reinstalled when they change, speeding up rebuilds.
Why must secrets never go into an image?
Show the answer
Anyone who has the image can extract them.
