The Docker Bay Six
Let’s look at the key instructions from our recipe, piece by piece.
These six instructions are not the only ones available, but they are the most common and fundamental instructions used in a Dockerfile.
FROM node:20-alpineThis sets the base image.
The Foundation: Base Images
Section titled “The Foundation: Base Images”A Base Image is a pre-packaged environment that already contains the basic OS tools and runtime we need.
Instead of manually installing Linux and Node.js from scratch every time, we start with an image that has them ready to go.
Where do they come from?
Section titled “Where do they come from?”Most base images are pulled from Docker Hub, the central registry for the Docker world.
When we specify FROM node:20-alpine, we are saying:
node: The official Node.js repository.20: The major version of the runtime.alpine: A specific variant based on Alpine Linux, which is incredibly tiny (around 5MB) compared to standard distributions. This keeps our final image fast and secure by removing “bloat.”
In a professional environment, always prioritize “Official” images (verified by Docker) over random community ones. It’s a matter of security. We don’t want to build our infrastructure on a foundation that we don’t trust.
WORKDIR
Section titled “WORKDIR”WORKDIR /appThis sets the working directory inside the image.
After this line runs, any later COPY, RUN, or CMD instructions will execute relative to /app inside the container. If the folder doesn’t exist, Docker creates it.
The WORKDIR instruction is not strictly necessary, but it is a good
practice to use it. It makes the Dockerfile easier to read and understand.
Also, WORKDIR does not define the root directory of the image. It only defines the working directory for subsequent instructions.
COPY package*.json ./This copies the package manifest files from our host machine into the working directory of the image. The ./ points directly to the WORKDIR we just established.
Later, the command:
COPY . .copies the rest of our application code across to the image.
The two dots in COPY . . refers to the build context, which is the set of
files and directories that are copied from the host machine to the image.
This is not the same as .. which refers to the parent directory. It’s
important to understand the difference between the two.
If you forget the space between the two dots, Docker will try to copy a file
named .. to the current directory, which will likely fail.
RUN npm installThis executes a shell command during the image build phase.
In this case, it installs the NPM dependencies and burns the resulting node_modules directory permanently into the image.
We can use as many RUN instructions as we need in order to build the
image. Most of the time, we will only need one or two, such as installing
dependencies (npm install) and building the application (npm run build).
EXPOSE
Section titled “EXPOSE”EXPOSE 3000This records the container’s intended listening port in the image metadata.
It does not publish the port to your computer by itself.
EXPOSE does not make the app reachable from your browser on its own.
Think of it as a note attached to the image that says, “This container expects to listen on port 3000.”
To actually reach the app from outside the container, we will still need to:
- map a host port to the container port when we run the container
- make sure the application inside the container is actually listening on that port
We’ll publish ports when we run the container a little later.
CMD ["npm", "run", "dev"]This sets the default command Docker runs when the container starts.
In this lesson, we want our container to start the Vite development server, so this is the command we use.
A Dockerfile should have only one active CMD.
If you write more than one, Docker uses only the last one.
Running npm Scripts with CMD
Section titled “Running npm Scripts with CMD”If our package.json contains this script:
"scripts": { "dev": "vite --host 0.0.0.0"}then this Dockerfile instruction:
CMD ["npm", "run", "dev"]tells Docker to run that script when the container starts.
If package.json has a script named “start”, we can use CMD ["npm", "start"].
start is a special npm script name.
Both npm start and npm run start do the same thing.
Container gotcha: 0.0.0.0 vs localhost
Section titled “Container gotcha: 0.0.0.0 vs localhost”Inside a container, localhost means “inside the container itself.”
So if Vite listens only on localhost, the dev server may be running, but our browser outside the container still won’t be able to connect to it.
That’s why our script uses:
"dev": "vite --host 0.0.0.0"This tells Vite to listen on all network interfaces inside the container, making it reachable through the mapped port.
RUN happens when the image is built.
CMD happens when a container is started from that image.
Extra Bits & Bytes
Section titled “Extra Bits & Bytes”Dockerfile Best Practices
⏭ Caching and Order of Operations
Section titled “⏭ Caching and Order of Operations”Layers are cached and only rebuilt when the instructions they depend on change.
This means that the order of instructions in a Dockerfile matters.