Lesson 3 of 6

The Dockerfile

So far you've run other people's images. The moment your own app needs to become an image, you write a Dockerfile — a small text file, checked into your repo, that lists the exact steps to build your app's environment from scratch. It's the "works on my machine" fix made literal: the machine setup is now code.

💡 The Bottom Line

A Dockerfile is an ordered list of instructions — start from a base image, copy your code in, install dependencies, declare the startup command. docker build executes it top to bottom, producing an image, and caches each step so unchanged steps never re-run.

A real, complete example

Here's a Dockerfile for a small Node.js web app — five instructions cover most apps you'll ever containerize:

FROM node:22-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

EXPOSE 3000
CMD ["node", "server.js"]

Build and run it:

docker build -t myapp:1.0 .
docker run -d -p 3000:3000 myapp:1.0

The -t names (tags) the image; the final . says "the build context is this folder."

Why the COPY is split in two

Every instruction creates a layer, and Docker caches layers: if an instruction and everything before it are unchanged, the cached result is reused. That's why the example copies package*.json and runs npm ci before copying the rest of the code. Your code changes constantly; your dependency list rarely does. Ordered this way, an ordinary code edit rebuilds in seconds because the slow npm ci layer comes straight from cache. Ordered the naive way (COPY . . first), every single edit re-installs all dependencies.

Rule of thumb: least-changing instructions at the top, most-changing at the bottom.

🙋 There Are No Dumb Questions

Q: What's the difference between RUN and CMD? Both run commands.
A: When they run. RUN executes while the image is being built — its result is baked into the image (installed packages, compiled files). CMD doesn't execute at build time at all; it just records the default command to execute later, every time a container starts. Build-time work goes in RUN; the app's startup command goes in CMD.

⚠️ Watch Out

COPY . . copies everything in your folder — including node_modules, .git, logs, and local secrets — unless you add a .dockerignore file. Create one alongside every Dockerfile (start with node_modules, .git, .env). It keeps images smaller, builds faster, and secrets out of the image.

🔧 Try It Yourself

No Node app handy? Containerize a static page instead — two lines:

FROM nginx:1.27-alpine
COPY index.html /usr/share/nginx/html/

Put any index.html next to that Dockerfile, then docker build -t mysite . and docker run -d -p 8080:80 mysite. Your own image, serving your own page, from a file you could commit to any repo.

✅ Quick Check

Why do Dockerfiles copy the dependency manifest and install dependencies before copying the rest of the source code?


Next up: the two things a disposable container can't provide on its own — data that survives, and network access — via volumes and port mapping.

← Back to
Next →