LearnAI ToolsCareerPractice BuildsPlayDevelopmentContact
Lesson 514 min read

Writing a Dockerfile: Containerise a Python App

Write your first Dockerfile for a Python Flask API, build it into an image, run it, and avoid the classic “listening on localhost” mistake.

On this page 10 sections

Introduction

So far you have run images other people built. Now you will build your own. A Dockerfile is a plain text file with step-by-step instructions; docker build follows them and produces an image you can run anywhere.

What You Will Learn
  • How to write a Dockerfile with FROM, WORKDIR, COPY, RUN, EXPOSE and CMD.
  • How to build an image with docker build and give it a name.
  • How to run and test your own containerised app.
  • Why a web app in a container must listen on 0.0.0.0.

The App

Priya’s Quotes API is a small Flask web app with two endpoints. Her project folder quotes-api contains three files:

quotes-api/
├── app.py
├── Dockerfile
└── requirements.txt
app.py
import os
import socket
from flask import Flask, jsonify
app = Flask(__name__)
QUOTES = [
"Talk is cheap. Show me the code.",
"First, solve the problem. Then, write the code.",
"Simplicity is prerequisite for reliability.",
]
@app.route("/")
def home():
greeting = os.environ.get("GREETING", "Hello from Docker!")
return jsonify(message=greeting, container=socket.gethostname())
@app.route("/quotes")
def quotes():
return jsonify(QUOTES)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
requirements.txt
flask==3.1.3

Notice host="0.0.0.0" on the last line — it matters a lot, as you will see below.

The Dockerfile, Line by Line

The file must be called Dockerfile (no extension) and sits in the project folder:

Dockerfile
FROM python:3.14-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 5000
CMD ["python", "app.py"]
InstructionWhat it does here
FROM python:3.14-slimStart from the official slim Python 3.14 image — Python is already installed
WORKDIR /appCreate /app inside the image and make it the current folder for the following steps
COPY . .Copy everything from the project folder (the build context) into /app
RUN pip install ...Run a command while building — here, install Flask into the image. --no-cache-dir keeps pip’s download cache out of the image
EXPOSE 5000Document that the app listens on port 5000 (it does not publish the port)
CMD ["python", "app.py"]The command a container runs when it starts
Use the JSON Form for CMD

Write CMD ["python", "app.py"] rather than CMD python app.py. The JSON (exec) form runs your program directly, so it receives stop signals properly and shuts down cleanly when you run docker stop.

Building the Image

From inside the project folder, build the image and name it with -t (tag). The final . is the build context — the folder whose files COPY can use:

cd quotes-api
docker build -t quotes-api .

Docker prints each step as it runs: loading the Dockerfile, pulling python:3.14-slim if needed, then [1/4] FROM, [2/4] WORKDIR, [3/4] COPY and [4/4] RUN pip install, and finally naming to docker.io/library/quotes-api:latest. If a step fails, the build stops and shows that step’s error output. Check the result:

docker images quotes-api

Running the Container

Run it like any other image — in the background, with a name and a published port:

docker run -d --name api -p 5000:5000 quotes-api
curl http://localhost:5000/
curl http://localhost:5000/quotes

The first request returns JSON with the greeting and the container’s hostname (its short ID); the second returns the list of quotes. docker logs api shows Flask starting up and one line per request. Flask also warns that it is a development server — lesson 14 replaces it with a production server.

The Classic Mistake: Listening on localhost

If app.py used app.run(port=5000) without host="0.0.0.0", Flask would listen only on 127.0.0.1 inside the container. Requests forwarded by -p arrive on the container’s network interface, not its loopback, so curl fails with “Connection reset by peer” even though docker logs shows the app running. Any server in a container must listen on 0.0.0.0.

Mini Challenge · Spot What's Missing

Priya runs docker build -t quotes-api from inside her project folder and Docker reports an error about a missing argument. What is missing?

Choose one answer

Dockerfile Instructions Reference

InstructionPurpose
FROMThe base image to start from (every Dockerfile begins with it)
WORKDIRSet the working folder for later instructions and for the container
COPY / ADDCopy files into the image (prefer COPY; ADD can also fetch URLs and unpack archives)
RUNRun a command at build time, e.g. install packages
ENVSet an environment variable for build steps and containers
ARGA variable available only while building (lesson 9)
EXPOSEDocument the port the app listens on
USERRun later steps and the container as a given user (lesson 14)
CMDThe default command when a container starts
ENTRYPOINTA fixed command that CMD’s values are passed to (lesson 6)
HEALTHCHECKA command Docker runs to check the app is healthy (lesson 14)
Mini Challenge · Put It in Order

Put the lines of a simple Python Dockerfile in the right order.

  1. EXPOSE 5000
  2. WORKDIR /app
  3. COPY . .
  4. FROM python:3.14-slim
  5. RUN pip install --no-cache-dir -r requirements.txt
  6. CMD ["python", "app.py"]

Common Mistakes

Avoid These Mistakes
  • Forgetting the . (build context) at the end of docker build.
  • A server listening on 127.0.0.1 instead of 0.0.0.0 inside the container.
  • Saving the file as Dockerfile.txt — it must be exactly Dockerfile.
  • Rebuilding but running an old container — remove the old container and start a new one from the new image.

Frequently Asked Questions

No. The image contains a copy of your files from build time. Rebuild with docker build after every change, then start a new container.

Yes, with docker build -f Dockerfile.dev -t myapp . — useful for keeping separate development and production versions.

python on its own means python:latest, which can change version without warning and is much larger. Pinning 3.14-slim gives a predictable, smaller image.

Key Takeaways

  • A Dockerfile lists the steps to build an image: FROM, WORKDIR, COPY, RUN, EXPOSE, CMD.
  • docker build -t name . builds an image from the current folder.
  • Run your image like any other: docker run -d -p 5000:5000 --name api quotes-api.
  • Servers in containers must listen on 0.0.0.0.
  • Use the JSON form of CMD so your app shuts down cleanly.

Summary

You have containerised your first app. Next you will make builds fast with the build cache, keep junk out of images with .dockerignore, and version your images with tags.

Next Lesson →

Faster, Cleaner Builds: Cache, .dockerignore and Tags