A Dockerfile is a plain-text file containing a series of instructions that Docker uses to build an image automatically. The build is triggered with docker build -t name:tag ., where the trailing dot specifies the current directory as the build context. Each instruction in the Dockerfile creates a new layer in the resulting image, and the order of those instructions matters for both correctness and efficiency.
The Dockerfile always begins with a FROM instruction, which selects a base image that everything else is built on top of, such as ubuntu:20.04. From there, RUN executes shell commands during the build (for example, RUN apt-get update) and each RUN creates a new image layer. WORKDIR sets the working directory used by subsequent instructions and creates it if it does not already exist, while CMD provides default arguments for the command that will run when a container starts. ENTRYPOINT goes a step further by configuring the container's main executable, with any CMD values passed as arguments to that executable. USER switches to a non-root user for the following instructions, which is a key security practice, and HEALTHCHECK defines a command that Docker runs periodically inside the container to report whether the service is actually healthy.
Several techniques help produce smaller, faster, more secure images. Multi-stage builds use multiple FROM statements so that compilers and build tools can live in an early stage while only the final artifacts are copied into a slim runtime stage. Layer caching means Docker reuses any layer whose inputs have not changed, so it pays to place stable, rarely-modified instructions early and to combine related shell commands onto a single RUN line. A .dockerignore file works like a .gitignore, excluding files such as node_modules from the build context. Together with choosing minimal base images, these practices reduce image size and improve build performance.