Docker Lesson 11 –Dockerfile Introduction | Dataplexa
Section II · Lesson 11

Dockerfile Introduction

Up until now, you've been running images other people built. Section II is where you start building your own. A Dockerfile is the recipe — and this lesson teaches you exactly how to read and write one.

Every Docker image you've ever pulled — nginx, postgres, node, redis — started as a Dockerfile. Someone wrote a set of instructions, ran docker build, and pushed the result to Docker Hub. By the end of Section II, you'll be doing the exact same thing with your own applications.

A Dockerfile Is a Build Script

A Dockerfile is a plain text file — no extension, just the name Dockerfile — containing a sequence of instructions that tell the Docker Daemon how to build an image. Each instruction becomes a layer in the final image.

The Daemon reads the Dockerfile top to bottom, executes each instruction in order, and stacks the resulting layers into a complete image. When you run docker build, that's what's happening under the hood — the Daemon is following your recipe, one step at a time.

The Chef's Recipe Analogy

A Dockerfile is a list of instructions sent to a chef. Each instruction creates a new action on top of the previous one — start with a base kitchen (FROM), install your equipment (RUN), bring in your ingredients (COPY), and tell the chef what to cook when the restaurant opens (CMD). The final dish — the image — is the result of all those instructions executed in order. Hand the recipe to any kitchen in the world and they'll produce the identical dish.

The Core Dockerfile Instructions

A Dockerfile has many possible instructions, but six of them cover the vast majority of real-world use cases. Learn these six and you can Dockerize almost any application.

FROM

Sets the base image

Every Dockerfile must start with FROM. It defines the starting point — the OS and runtime your image is built on top of. Example: FROM node:18-alpine.

RUN

Executes a command during the build

Runs a shell command inside the image at build time — installing packages, creating directories, compiling code. Each RUN creates a new layer. Example: RUN npm install.

COPY

Copies files from host into the image

Takes files from your local machine (the build context) and places them inside the image filesystem. Example: COPY . /app copies everything in the current directory to /app inside the image.

WORKDIR

Sets the working directory

All subsequent RUN, COPY, and CMD instructions execute relative to this directory. Creates the directory if it doesn't exist. Example: WORKDIR /app.

EXPOSE

Documents the port the app listens on

Tells Docker and other developers which port the containerised application uses. It's documentation — it does not actually publish the port. You still need -p in docker run to expose it to the host. Example: EXPOSE 3000.

CMD

Sets the default command to run

Defines the command that runs when a container starts from this image. Only one CMD is allowed — the last one wins. It can be overridden at runtime. Example: CMD ["node", "server.js"].

Reading a Real Dockerfile

The scenario: You've just joined a startup as a backend developer. The team's Node.js API already has a Dockerfile in the repository. Before you run anything, you want to understand exactly what it does — every instruction, every decision, every layer it creates.

FROM node:18-alpine
# Start from the official Node 18 image built on Alpine Linux
# Alpine keeps the base image tiny — ~35 MB vs ~900 MB for the full Debian-based node image

WORKDIR /app
# All subsequent instructions run relative to /app inside the container
# If /app doesn't exist, Docker creates it automatically

COPY package*.json ./
# Copy package.json and package-lock.json (the * matches both) into /app
# We copy these BEFORE the source code — this is a deliberate caching trick (more in Lesson 14)

RUN npm install
# Install all Node.js dependencies declared in package.json
# This runs at BUILD time, not at runtime — the node_modules end up baked into the image

COPY . .
# Now copy the rest of the application source code into /app
# The dot on the right means "current WORKDIR" — i.e. /app

EXPOSE 3000
# Document that the app listens on port 3000
# This is metadata — it does not publish the port to the host

CMD ["node", "server.js"]
# The default command to run when a container starts from this image
# Array syntax (exec form) is preferred — it runs node directly, not through a shell
[+] Building 18.3s (9/9) FINISHED
 => [internal] load build definition from Dockerfile                    0.0s
 => [internal] load .dockerignore                                       0.0s
 => [internal] load metadata for docker.io/library/node:18-alpine       1.2s
 => [1/5] FROM node:18-alpine                                           3.4s
 => [2/5] WORKDIR /app                                                  0.0s
 => [3/5] COPY package*.json ./                                         0.1s
 => [4/5] RUN npm install                                              11.8s
 => [5/5] COPY . .                                                      0.2s
 => exporting to image                                                  1.6s
 => => naming to docker.io/library/payment-api:latest                   0.0s

What just happened?

The build output shows five numbered steps — each one corresponds to an instruction in the Dockerfile. Step 1 is WORKDIR (FROM doesn't count as a build step — it's the base). Step 4 (RUN npm install) took 11.8 seconds — installing dependencies is always the slowest step, which is exactly why you want it cached. The final line names the resulting image. Each step that completed added a new read-only layer to the image. The total build time of 18.3 seconds includes pulling the base image — on the second build, that layer is cached and the total drops dramatically.

CMD vs RUN — The Confusion Everyone Hits

Every beginner confuses RUN and CMD at least once. Here's the clearest way to think about it:

RUN

Runs at build time

Executed when you run docker build. The result is baked into the image as a layer. Use it to install software, create files, compile code — anything that prepares the environment.

Example: RUN apt-get install -y curl

CMD

Runs at container start

Executed when someone runs docker run on the image. It's the default startup command. Not baked into the image — it's metadata that tells Docker what to launch.

Example: CMD ["node", "server.js"]

EXPOSE Does Not Open Ports

EXPOSE 3000 in your Dockerfile is documentation, not configuration. The port is not accessible from your host machine until you add -p 3000:3000 to your docker run command. Think of EXPOSE as leaving a sticky note — it tells other developers which port to map, but it doesn't do the mapping itself.

Teacher's Note

The order of instructions in a Dockerfile matters enormously for build speed. Lesson 14 covers layer caching in depth — but the single biggest habit to build right now is always copying package.json and running npm install before copying your source code.

Practice Questions

1. Every Dockerfile must begin with which instruction that sets the base image?



2. To install a package inside an image during the build process, which Dockerfile instruction do you use?



3. The Dockerfile instruction that defines the default command to run when a container starts is called what?



Quiz

1. What is the core difference between the RUN and CMD instructions in a Dockerfile?


2. A Dockerfile contains EXPOSE 8080. What does this actually do?


3. A Dockerfile contains WORKDIR /app followed by COPY . . — where does the COPY instruction place the files?


Up Next · Lesson 12

Writing Your First Dockerfile

You've read a Dockerfile — now you write one from scratch, build the image, and run a container from your own creation for the first time.