A working knowledge of every common instruction, since interviewers (and real Dockerfiles) expect you to know what each one actually does, not just recognize the syntax.
FROM node:20-alpine # the base image every subsequent layer builds on
WORKDIR /app # sets the working directory for everything after it (creates it if missing)
COPY package*.json ./ # copies files from build context into the image
ADD archive.tar.gz /app/ # like COPY, but also auto-extracts local tar archives and can fetch URLs
RUN npm ci # executes a command AT BUILD TIME, result is baked into a layer
ENV NODE_ENV=production # sets an environment variable available at build time AND runtime
ARG BUILD_VERSION=dev # a build-time-only variable, not present in the running container
EXPOSE 3000 # documentation only — doesn't actually publish the port (see Networking)
USER node # switches the user subsequent instructions and the container run as
VOLUME /app/data # declares a mount point intended for persistent data
CMD ["node", "server.js"] # the default command run when the container starts
COPY vs. ADD: ADD does everything COPY does, plus automatically extracts local .tar archives and can fetch remote URLs. In practice, the general advice is to prefer COPY for everything except the specific case of extracting a local archive — ADD's extra magic (especially the URL-fetching behavior) is a common source of confusing, hard-to-reproduce build behavior, and it doesn't leverage layer caching the way a separate RUN curl step would.
ENV vs. ARG: ARG only exists during the build (passed via docker build --build-arg KEY=value) and is gone once the image is built — it never appears in the running container or docker inspect. ENV is baked into the image and is present in every container started from it. A common pattern combines them — pass a version as a build ARG, then bake it into an ENV if the running application needs to read it:
ARG APP_VERSION=dev
ENV APP_VERSION=${APP_VERSION}
Never put secrets in ARG — build arguments are visible in docker history on the final image and in the build cache, so an API key passed via --build-arg is not actually private. Use Docker's --secret build flag (with RUN --mount=type=secret) for anything sensitive that a build step genuinely needs to read.