A Dockerfile is a plain text file with one instruction per line. Docker runs the instructions top to bottom to build an image. The order matters, because Docker reuses earlier steps when nothing above them has changed.
Learning Objectives
- Read a Dockerfile and say what each instruction does.
- Explain what EXPOSE does and doesn't do.
- Order instructions so rebuilds stay fast.
A Simple Dockerfile
FROM python:3.10-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["python", "app.py"]
What Each Instruction Does
| Instruction | Purpose |
|---|---|
| FROM | The base image to build on |
| WORKDIR | The folder commands run in inside the image |
| COPY | Copies files from your project into the image |
| RUN | Runs a command while building, such as installing packages |
| EXPOSE | Documents the port the app listens on |
| CMD | The command a container runs when it starts |
A Real Detail: EXPOSE Doesn't Open a Port
EXPOSE only documents which port the app uses. It doesn't make the port reachable from your computer. To publish it, you map it when you run the container with -p, for example docker run -p 8000:8000. Docker's own documentation says EXPOSE is documentation between the person who builds the image and the person who runs it.
Why the Order Matters
Copying requirements.txt and installing packages before copying the rest of the code means that changing a line of application code doesn't reinstall every package. Docker reuses the cached install step, so rebuilds take seconds instead of minutes.
Build It
docker build -t my-python-app . docker run -p 8000:8000 my-python-app
The . at the end tells Docker to use the current folder as the build context, which is where COPY reads files from.
Learning Check
Why put COPY requirements.txt before COPY . .?
So the package install step is cached. Changing application code only reruns the final COPY, not the install.
Does EXPOSE 8000 make the app reachable from my browser?
No. You also need -p 8000:8000 when running the container.
Step by Step: Build and Run Your Own Image
- Create a project folder with your app file and a requirements.txt listing its packages.
- Create a file named Dockerfile (no extension) with the instructions from above.
- Create a .dockerignore file that lists files Docker should not copy, such as .git and __pycache__.
- Build the image: docker build -t my-python-app .
- Run it with a port mapping: docker run -p 8000:8000 my-python-app, then open http://localhost:8000.
Common Mistakes
Copying all the code before installing packages
Any code change then invalidates the install cache, so every build reinstalls everything. Copy requirements.txt first, install, then copy the rest.
Forgetting a .dockerignore file
Without it, COPY . . also copies .git folders, local virtual environments, and secret files into the image.
Assuming EXPOSE makes the port reachable
EXPOSE only documents the port. Publish it with -p when you run the container.
Running the app as root by default
Many images run processes as root. Create a non-root user in the Dockerfile for production images.
Interview Questions
What's the difference between CMD and ENTRYPOINT?
ENTRYPOINT sets the executable that always runs. CMD provides default arguments to it, and those can be overridden when you run the container.
Why does instruction order matter in a Dockerfile?
Docker caches each instruction's result. Putting rarely changing steps first means changes lower down reuse the cache above them.
What does a .dockerignore file do?
It lists files and folders excluded from the build context, which keeps images smaller and stops secrets or local files from being copied in.
Summary
A Dockerfile lists the steps that build an image. Put the instructions that change least often near the top so Docker can reuse them, and remember that EXPOSE documents a port while -p publishes it.