From unit 2, in docker run -d -p 8080:80 --name web nginx, the word nginx refers to the image the container is created from.
The container's name is set by --name web, and the final positional word is always the image.
So far this course has only used other people's images such as nginx and python. This unit is where you build your own.
The Dockerfile
So far you ran images other people built. To containerize your app, you write a Dockerfile: a plain text file, literally named Dockerfile, that lists the steps to assemble your image.
Here is a complete one for a small Python web app:
FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . EXPOSE 8000 CMD ["python", "server.py"]
Seven lines, and each one is a simple instruction: INSTRUCTION arguments. Docker executes them top to bottom to produce an image. Let's decode every line.
Line by line
FROM python:3.12-slim: every image starts from a base image. Instead of building an OS from scratch, you stand on one that already has Python 3.12. Note the pinned tag, as promised in lesson 2-1WORKDIR /app: cd into/appinside the image (creating it), so later instructions run thereCOPY requirements.txt .: copy a file from your project folder into the image's current directoryRUN pip install -r requirements.txt: execute a command during the build and save the result into the imageCOPY . .: now copy the rest of your source code inEXPOSE 8000: documentation that the app listens on port 8000. It does not publish the port,-pstill does that at run timeCMD ["python", "server.py"]: the default command a container runs when started. Exactly one per image, and it runs atdocker runtime, not build time
The same pattern in any language
The Dockerfile above is not Python-specific, it is a template. Here is the Node.js version of the exact same idea:
FROM node:22-slim WORKDIR /app COPY package.json package-lock.json ./ RUN npm install COPY . . EXPOSE 3000 CMD ["node", "server.js"]
Same skeleton, different base image and package manager: pinned slim base, dependency manifest copied first, dependencies installed, then the code. Once you can read one Dockerfile you can read almost all of them, because 90% of real Dockerfiles are this seven-line shape with the language swapped. (Why the manifest is copied before the code is the star of lesson 3-3.)
Building it
docker build -t myapp:v1 .
-t myapp:v1: tag the resulting image with a name and version, so you candocker run myapp:v1later.: the build context, the folder whose filesCOPYis allowed to see. The dot means the current directory
The workflow you now own end to end:
git clone <your project> docker build -t myapp:v1 . docker run -d -p 8080:8000 myapp:v1
Anyone with Docker can run those three commands on any machine and get an identical running app. That is the fix for lesson 1-1's broken server.
RUN against CMD
RUN executes at build time and bakes the result into the image, while CMD sets the default command executed when a container starts.
| Instruction | When it runs | Where the effect lands |
|---|---|---|
RUN | during docker build | in an image layer |
CMD | when a container starts | nowhere, it is metadata |
RUN happens once, during the build, and its effects such as installed packages are stored in the image. CMD is recorded metadata that runs every time someone starts a container from the image.
A Dockerfile can have many RUN instructions but only one effective CMD, since a later one simply replaces the earlier. Confusing the two is a common early mistake, and the symptom is a package that appears to install on every container start instead of once at build.
The instruction that picks the base image
The instruction is FROM.
It declares the base image, for example FROM python:3.12-slim, and everything else in the Dockerfile adds to that starting point. That is why FROM is essentially required to be the first instruction.
| Line | Role |
|---|---|
FROM python:3.12-slim | the starting point |
WORKDIR /app | where later instructions operate |
COPY and RUN | additions on top |
CMD | what to run at container start |
Choosing the base image is the single most consequential line in a Dockerfile, because it decides the image size, the available package manager, and most of the security surface before any of your own code is added.