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"]
FROM— the base image to build on.node:22-alpinemeans "Node 22 on Alpine Linux," a tiny base that keeps your final image small.WORKDIR— the folder inside the image where subsequent commands run (created if missing).COPY— copy files from your project into the image.RUN— execute a command at build time (installing dependencies, compiling).CMD— the command to run when a container starts. There can be only one; the last one wins.
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.