Creating Your First Dockerfile: From Empty File to Running Container

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 /app inside 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:

  • RUN happens at build time
  • CMD happens 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:

PhaseHappens WhenInstructions
Build timeWhen creating the imageFROM, RUN, COPY
Run timeWhen starting containerCMD

If you think of an image as a frozen snapshot, then:

  • RUN and COPY shape the snapshot
  • CMD tells 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.