If you’re learning Docker, one of the first concepts you’ll encounter is the Dockerfile.

A Dockerfile may initially look like a collection of unfamiliar instructions:
FROM
RUN
COPY
ENV
CMD
However, each instruction has a specific purpose. Once you understand how they work together, writing Dockerfiles becomes much easier.
What is a Dockerfile?
A Dockerfile is a set of instructions used to build a Docker image.
The basic workflow looks like this:
Dockerfile
↓
docker build
↓
Docker Image
↓
docker run
↓
Docker Container
You can think of a Dockerfile as a blueprint that defines the environment your application needs.
For example, a Dockerfile can tell Docker to:
- Start with a Node.js environment
- Create a working directory
- Copy application files
- Install dependencies
- Define environment variables
- Start the application
Let’s look at the most common instructions.
1. FROM — Choose the Base Image
A Dockerfile generally starts with the FROM instruction.
For a Node.js application:
FROM node:20
This tells Docker to use the Node.js 20 image as the starting point for our image.
Instead of starting with a minimal operating system and manually installing Node.js, we can use an existing image that already contains Node.js.
For example:
FROM node:20
provides a base environment in which Node.js is already installed.
In simple terms:
FROM defines the starting point for your image.
2. ENV — Define Environment Variables
The ENV instruction allows you to define environment variables:
ENV PORT=3000
ENV NODE_ENV=production
These variables are then available inside the container.
For example, a Node.js application can access the port using:
const port = process.env.PORT;
However, environment-specific configuration is often better provided when the container is run rather than permanently embedded in the image.
This is particularly useful for values such as:
- Database connection strings
- API configuration
- Application settings
- Development and production configuration
The important concept is:
ENV defines configuration that is available in the container environment.
3. RUN — Execute Commands During the Build
The RUN instruction executes commands while the Docker image is being built.
For example:
RUN mkdir /app
creates the /app directory inside the image.
Another common example is:
RUN npm install
which installs the application’s dependencies during the image build process.
You can have multiple RUN instructions:
RUN apt-get update
RUN npm install
The key point is that RUN happens during:
docker build
It is used primarily to prepare the image.
4. COPY — Copy Application Files into the Image
Your application files normally exist in your project directory on your machine.
For example:
my-app/
├── Dockerfile
├── package.json
├── package-lock.json
└── server.js
You can copy these files into the image using:
COPY . /app
This tells Docker to take files from the build context and place them in /app inside the image.
This is different from RUN.
For example:
RUN mkdir /app
executes a command as part of building the image.
Whereas:
COPY . /app
transfers files from the build context into the image.
A useful way to remember this is:
RUN executes a command; COPY transfers files into the image.
5. CMD — Define the Default Startup Command
The CMD instruction specifies the default command that should be executed when a container starts.
For a Node.js application:
CMD ["node", "server.js"]
This tells Docker:
When a container is started from this image, run
node server.js.
The overall flow is:
docker build
↓
Docker Image
↓
docker run
↓
Container starts
↓
CMD executes
↓
node server.js
This is why CMD is commonly used to start the application's main process.
RUN vs CMD
One of the most important distinctions to understand when learning Docker is the difference between RUN and CMD.
RUN
RUN npm install
Executes during the image build process.
CMD
CMD ["node", "server.js"]
Executes when a container is started.
A simple way to remember the difference:
RUN prepares the image.
CMD starts the application.
For example:
docker build
↓
RUN npm install
↓
Docker Image
↓
docker run
↓
CMD ["node", "server.js"]
↓
Application starts
A Complete Example
Here is a simple Dockerfile for a Node.js application:
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ENV PORT=3000
CMD ["node", "server.js"]
Let’s translate each instruction into plain English:
FROM node:20
→ Start with the Node.js 20 image.
WORKDIR /app
→ Set /app as the working directory.
COPY package*.json ./
→ Copy the package files into the image.
RUN npm install
→ Install the application's dependencies.
COPY . .
→ Copy the remaining application files.
ENV PORT=3000
→ Define the PORT environment variable.
CMD ["node", "server.js"]
→ Start the Node.js application when the container runs.
Dockerfile Cheat Sheet
InstructionPurposeFROMSelects the base imageENVDefines environment variablesRUNExecutes commands during image buildingCOPYCopies files into the imageCMDDefines the default command when the container startsWORKDIRSets the working directory
The Bigger Picture
A Dockerfile is essentially a way of describing the environment and instructions required to run an application.
Instead of manually setting up an environment every time, you define the setup once:
Choose a base environment
↓
Configure the environment
↓
Copy the application
↓
Install dependencies
↓
Define the startup command
↓
Build the Docker image
↓
Run the container
This makes application environments more consistent and reproducible across development, testing, and production.
Key Takeaways
If you’re just getting started with Docker, focus on these concepts first:
1. Image and container are different.
A Docker image is a packaged, immutable template. A container is a running instance of that image.
2. RUN and CMD serve different purposes.
RUN executes during image construction, while CMD defines what runs when the container starts.
3. A Dockerfile is a set of instructions.
It describes how to build an environment in which your application can run consistently.
Once you understand these fundamentals, concepts such as Docker Compose, volumes, networking, multi-stage builds, and container orchestration become much easier to learn.