The Core Instructions
A Dockerfile Is a Recipe
Every image you've used so far (nginx:alpine, redis:7-alpine) was built by someone else and published to a registry. To build your own image, you write a Dockerfile - a plain text file, always named exactly Dockerfile, that lists the steps needed to assemble it: start from some base, copy in files, install things, and say how to start the app. Docker reads it top to bottom and turns each line into one of those layers from the last module.
FROM node:20-alpine # start from a base image
WORKDIR /app # cd (creates the dir)
COPY package*.json ./ # copy files from build context
RUN npm ci # run a command, bake result into a layer
COPY . .
ENV NODE_ENV=production # default env var
EXPOSE 3000 # documentation: app listens here
USER node # drop root before running
CMD ["node", "server.js"] # default command when container starts
$ docker build -t myapp:v1 . # . = build context (what COPY can see)
| Instruction | Runs when | Common mistake |
|---|---|---|
RUN | at build time | using it to start servers (they die when the step ends) |
CMD | at container start | shell form breaks signal handling - prefer the JSON array form |
COPY | at build time | wrong source path → "not found" build errors |
EXPOSE | never (metadata only) | thinking it publishes the port - you still need -p |
Tip: Read build errors from the first failure.COPY site/ /xfailing with "not found" means the path doesn't exist in the build context - check what's actually in the directory you passed todocker build.