Course outline · 0% complete

0/29 lessons0%

Course overview →

Your first Dockerfile

lesson 3-1 · ~14 min · 7/29

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-1
  • WORKDIR /app: cd into /app inside the image (creating it), so later instructions run there
  • COPY requirements.txt .: copy a file from your project folder into the image's current directory
  • RUN pip install -r requirements.txt: execute a command during the build and save the result into the image
  • COPY . .: now copy the rest of your source code in
  • EXPOSE 8000: documentation that the app listens on port 8000. It does not publish the port, -p still does that at run time
  • CMD ["python", "server.py"]: the default command a container runs when started. Exactly one per image, and it runs at docker run time, not build time
Dockerfile the recipe build image read-only run container container container
The build and run cycle. A Dockerfile is a recipe, docker build turns it into an image once, and docker run creates containers from that image as often as needed.

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 can docker run myapp:v1 later
  • .: the build context, the folder whose files COPY is 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.

InstructionWhen it runsWhere the effect lands
RUNduring docker buildin an image layer
CMDwhen a container startsnowhere, 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.

LineRole
FROM python:3.12-slimthe starting point
WORKDIR /appwhere later instructions operate
COPY and RUNadditions on top
CMDwhat 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.