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)
InstructionRuns whenCommon mistake
RUNat build timeusing it to start servers (they die when the step ends)
CMDat container startshell form breaks signal handling - prefer the JSON array form
COPYat build timewrong source path → "not found" build errors
EXPOSEnever (metadata only)thinking it publishes the port - you still need -p
Tip: Read build errors from the first failure. COPY site/ /x failing with "not found" means the path doesn't exist in the build context - check what's actually in the directory you passed to docker build.