In a Dockerfile, there are many instructions, but as is often the case, in day-to-day use, only 4 or 5 of them will build 90% of your images.
In this article, we’ll look at the most common instructions and commands in Dockerfiles: the main “verbs” like FROM, WORKDIR, COPY, RUN, and CMD (plus a couple more at the end).
| Command | Description |
|---|---|
FROM | Defines the base image from which the container starts. |
WORKDIR | Sets the working directory for subsequent instructions. |
COPY | Copies files and folders from your local machine into the image. |
RUN | Executes commands during the image build process. |
CMD | Defines the default process that runs when the container starts. |
FROM
Defines which operating system or environment you will work on. The FROM instruction always comes first in a Dockerfile (except in very, very advanced cases).
FROM node:18-alpine
- Building a Python app?
FROM python:3.9. - Need full control?
FROM ubuntu:22.04. - Want something tiny?
FROM alpine.
Docker will try to pull this image from Docker Hub if you don’t have it locally.
WORKDIR
By default, Docker starts at the root / of the filesystem ❗❗❗❗. If you start copying things there, you’ll end up mixing your code with system directories (/bin, /etc…). A mess.
To change the working directory, we use WORKDIR.
WORKDIR /app
WORKDIR does two things:
- Creates the directory if it doesn’t exist.
- Runs
cdto that directory. All subsequent instructions will be executed there.
Always use it right after FROM. Keep your code organized in /app, /usr/src/app, or similar.
COPY
We have a base and a working directory. Now we need to copy our code in.
The syntax is
COPY <source_on_your_pc> <destination_in_image>
- The source is relative to where the Dockerfile is located (the Build Context).
- The destination can be relative to the
WORKDIR(which is why we use the dot .) or absolute.
For example,
COPY package.json .
COPY src/ ./src/
Sometimes you’ll see older tutorials using ADD. It’s the big brother of COPY. It does the same thing but can also download URLs and automatically extract .tar.gz files.
Always use COPY as it’s more explicit. Only use ADD if you really need to automatically extract an archive.
RUN
RUN executes commands during the build of the image. It is mainly used for:
- Installing dependencies (
apt-get install,npm install,pip install). - Configuring the environment.
- Compiling code.
RUN apt-get update && apt-get install -y git
RUN npm install
RUN is executed only once when you run docker build. The result is “frozen” into the image. It does not execute when you start the container.
Each RUN instruction creates a new layer in the image. That’s why you’ll often see commands chained with && to have everything done in a single layer, making the image smaller:
# Done right (1 layer)
RUN apt-get update && apt-get install -y \
python3 \
git \
&& rm -rf /var/lib/apt/lists/*
CMD
We have the image ready (build). Now, what should the container do when someone runs docker run?
CMD (Command) defines the default process to run on startup.
CMD ["node", "app.js"]
There can only be one CMD in the Dockerfile (if you have multiple, only the last one applies).
Bonus Additional Commands
As we mentioned, here are 3 more commands that aren’t as critical as the ones we just saw, but are still very common.
| Command | Description |
|---|---|
ENV | Defines environment variables that will live inside the container. |
EXPOSE | Documents the port the application will be listening on. |
ENTRYPOINT | Turns your container into a strict executable command. |
Applications need configuration (database connections, keys, production or development environments). ENV allows you to inject these variables so your application can read them on startup.
ENV NODE_ENV=production
ENV PUERTO=8080
The nice thing about using ENV is that you set a default value in the image, but the user can easily change it when starting the container using the -e flag (e.g., docker run -e PUERTO=9000 mi-app).
This is the most misunderstood command in all of Docker (and rightfully so, because technically it doesn’t do anything).
EXPOSE 8080
Putting EXPOSE 8080 in your Dockerfile does NOT make your container accessible from the internet. It doesn’t open any physical port or modify the firewall.
So, what’s it for? It is purely informative and for documentation. It’s your way of telling the next developer reading your code: “Hey, the application inside this container is programmed to emit data on port 8080.”
To actually open the port and connect it to the outside world, you will always need to use the -p flag when running the container (docker run -p 80:8080).
While CMD defines a default command, ENTRYPOINT tells Docker that this container is not a general-purpose system, but a single-purpose tool.
It turns your container into a strict executable command that cannot be easily overridden.
ENTRYPOINT ["python3", "processing_script.py"]
If you add this, your container will always, under any circumstance, execute that Python script upon startup.
