Creating Your First Dockerfile: From Empty File to Running Container
Using pre-built images from Docker Hub is convenient, but the real power of Docker appears when you start building your own images. That’s where Dockerfiles come in.
A Dockerfile is a simple text file that describes how to build an image step by step. It’s not code in the traditional sense — it’s more like a recipe that tells Docker exactly how to prepare an environment for your application.
Let’s look at how to write a small one from scratch and what the most important instructions actually do.
What Is a Dockerfile?
A Dockerfile is a list of instructions that Docker reads from top to bottom. Each instruction creates a new layer in the image.
These layers are:
- Cached and reusable
- Stacked on top of each other
- Shared between images when possible
This layered approach is why Docker can rebuild images quickly when only small parts change.
You don’t run a Dockerfile directly. You build an image from it, and then you run containers from that image.
A Very Simple Example
Here’s what a tiny Dockerfile for a basic application might look like:
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]
Even without knowing the details, you can already guess what’s happening:
- Start from a Node.js base image
- Put files into the container
- Install dependencies
- Run the app
That’s the core idea of every Dockerfile.
Now let’s break down the most important instructions.
FROM: Choosing a Starting Point
Every Dockerfile begins with FROM.
It defines the base image your image will be built on top of. This base image usually contains:
-
A minimal operating system
-
Language runtimes or system tools
For example:
- FROM ubuntu → general Linux system
- FROM python:3.12 → Linux with Python preinstalled
- FROM node:20-alpine → small Linux with Node.js
Instead of building everything from scratch, you stand on top of carefully maintained base images that already contain what you need.
Choosing the right base image affects:
- Image size
- Security
- Startup speed
So it’s an important decision.
RUN: Executing Commands During Build
The RUN instruction executes commands while the image is being built.
This is where you:
- Install packages
- Compile code
- Set up tools
For example:
RUN apt-get update && apt-get install -y curl
This command runs once during image creation and becomes part of the final image. It does not run when containers start.
A common mistake is expecting RUN to execute every time the container runs. It doesn’t — it only affects how the image is built.
COPY: Bringing Files Into the Image
Your application code usually lives on your computer, not inside the image. The COPY instruction moves files from your project directory into the image.
Example:
COPY . /app
This means:
- Take everything from the current folder
- Place it into
/appinside the image
Once copied, those files are part of the image and available when containers run.
This is how your source code, configuration files, and assets get inside the container.
CMD: What Runs When the Container Starts
CMD defines the default command that runs when a container starts from the image.
This is usually the main program of your application:
CMD \["node", "server.js"]
When someone runs a container from this image, Docker launches that command automatically.
Important detail:
RUNhappens at build timeCMDhappens at container runtime
Only one CMD instruction should exist in a Dockerfile. If multiple are present, only the last one takes effect.
Build Time vs Run Time
This difference is critical to understanding Dockerfiles:
| Phase | Happens When | Instructions |
|---|
| Build time | When creating the image | FROM, RUN, COPY |
|---|
| Run time | When starting container | CMD |
|---|
If you think of an image as a frozen snapshot, then:
RUNandCOPYshape the snapshotCMDtells it what to do when it wakes up
This mental model helps avoid many beginner mistakes.
Layering and Caching
Each Dockerfile instruction creates a new image layer. Docker caches these layers, which means:
- If a step hasn’t changed, Docker reuses it
- Only modified steps are rebuilt
That’s why instruction order matters.
If you copy your whole project and then install dependencies, changing one file may force everything to rebuild. If you install dependencies first and copy code later, rebuilds become faster.
Small structural decisions in Dockerfiles can have big performance impacts during development.From Dockerfile to Running Container
The full flow looks like this:
- Write a Dockerfile
- Build an image from it
- Run containers from that image
The Dockerfile defines how the image is made. The image defines what environment exists. The container is the running instance doing actual work.
Each layer builds on the previous one, creating a predictable and portable environment for your software.
Why Dockerfiles Are So Important
Dockerfiles are more than build scripts — they are documentation for your runtime environment.
Anyone who has the Dockerfile can recreate:
- The same OS
- The same dependencies
- The same startup behavior
That makes projects easier to:
- Share
- Deploy
- Reproduce months later
In a world where “it works on my machine” used to be normal, Dockerfiles turn environments into versioned, repeatable artifacts.
Small File, Big Control
At first glance, a Dockerfile looks simple — just a few lines of text. But those lines control everything about how your application runs in production.
Once you understand the basics of FROM, RUN, COPY, and CMD, you already know enough to package real applications into containers.
From there, you can move on to more advanced features like environment variables, multi-stage builds, and Docker Compose.
But every containerized system starts with one small file: the Dockerfile.