Build and run a small Python web app in a container
Write a tiny Flask app, give it a Dockerfile, build the image, run it with a published port, make a change and rebuild, then clean up. A first hands-on project with Docker from an empty folder.
Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?
What you will build
A web page served by a small Python (Flask) application that runs inside a Docker container. You will write the app, describe its image in a Dockerfile, build the image, run it on port 8000, look at its logs, change the code and rebuild, and finally remove everything. By the end you will have used the handful of commands that cover most daily Docker work.
Before you start
- Docker installed and working:
docker run hello-worldprints a greeting. On Linux see the entry on installing Docker Engine; on macOS and Windows use Docker Desktop. - A terminal and a text editor. No Python installation is needed on your machine; Python runs inside the container.
- About 20 minutes and roughly 200 MB of disk for the base image.
Step 1: Create the project folder and the app
mkdir hello-container
cd hello-container
Create app.py:
import os
import socket
from flask import Flask
app = Flask(__name__)
@app.get("/")
def index():
name = os.environ.get("GREETING_NAME", "world")
return f"<h1>Hello, {name}!</h1><p>Served from container {socket.gethostname()}</p>"
@app.get("/health")
def health():
return {"status": "ok"}
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
Two things matter for containers here. The app listens on 0.0.0.0, not 127.0.0.1: inside the container, localhost is the container itself, and Docker’s port forwarding only reaches a process bound to all interfaces. And configuration comes from an environment variable, which is how containers are usually configured.
Create requirements.txt with the one dependency:
flask
Step 2: Write the Dockerfile
Create a file named exactly Dockerfile (no extension):
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
RUN useradd --create-home appuser
USER appuser
EXPOSE 8000
CMD ["python", "app.py"]
FROM python:3.13-slim: start from the official Python image, the slim variant without build tools.WORKDIR /app: all following paths are relative to/appinside the image.- Dependencies are installed before the source is copied, so editing
app.pylater does not re-runpip install. USER appuser: the app runs as a normal user, not root.CMD: the command the container runs when it starts.
Also create .dockerignore so stray files never end up in the image:
__pycache__
*.pyc
.git
Step 3: Build the image
docker build -t hello-container:1.0 .
docker image ls hello-container
The final . is the build context: the folder whose files COPY may use. The first build downloads the base image and installs Flask; expect a minute or so. -t names the image. The listing shows an image of a bit over 100 MB.
Step 4: Run the container
docker run -d --name hello -p 8000:8000 hello-container:1.0
docker ps
Open http://localhost:8000 in a browser, or from the terminal:
curl http://localhost:8000
curl http://localhost:8000/health
You should see the greeting with a hostname that looks like 3f1c9a2b7d4e. That is the container’s ID: each container gets its own hostname.
Step 5: Look at logs and get a shell inside
docker logs hello
docker exec -it hello sh
ls -la /app
whoami
exit
logs shows Flask’s request log. exec -it ... sh opens a shell in the running container; whoami prints appuser, confirming the USER instruction worked.
Step 6: Change the configuration without rebuilding
docker rm -f hello
docker run -d --name hello -p 8000:8000 -e GREETING_NAME=Docker hello-container:1.0
curl http://localhost:8000
The page now says Hello, Docker. Settings that differ between environments belong in environment variables, so the same image runs everywhere.
Step 7: Change the code and rebuild
Edit app.py and change the <h1> text, then build a new version and replace the running container:
docker build -t hello-container:1.1 .
docker rm -f hello
docker run -d --name hello -p 8000:8000 hello-container:1.1
curl http://localhost:8000
This build is much faster: every layer up to COPY app.py came from the cache. Both 1.0 and 1.1 exist locally now, so rolling back is just running the old tag again.
Check that it works
docker pslistshellowith statusUpand ports0.0.0.0:8000->8000/tcp.curl http://localhost:8000/healthreturns{"status":"ok"}.docker logs helloshows a line for each request you made.docker exec hello whoamiprintsappuser.
Clean up
docker rm -f hello
docker rmi hello-container:1.0 hello-container:1.1
docker image ls
The project folder stays; only the container and your two images are removed. Careful: docker rmi deletes the images, so rebuild with docker build if you want them back. You can also remove python:3.13-slim with docker rmi python:3.13-slim if you do not plan to build again.
Next steps
- Add a database and run both with Compose: see the entry on
docker composefor an app with a PostgreSQL database. - Replace Flask’s development server with a production server such as
gunicornin theCMD. - Push the image to a registry so you can run it on another machine: see the entry on tagging, pushing and pulling images.
- Read the Dockerfile entry for
ENTRYPOINT, multi-stage builds and cache ordering.
Official documentation
Did this work for you?
No votes yet. Be the first.
If it did not work, the report form below tells the editors what went wrong.
See also
- 0002 Python cheat sheet: strings, lists, dicts and f-strings The everyday methods for text, lists and dictionaries, plus f-string formatting and comprehensions, each with a one-line example you can paste into… Beginner · Cheat sheet · Any system Article Basics 15 Oct 2026
- 0005 Dataclasses in depth @dataclass writes __init__, __repr__ and __eq__ for classes that mainly hold data. Learn defaults, default_factory, frozen and ordered classes, __post_init__ validation, slots… Advanced · Explains an idea · Any system Article Basics 15 Oct 2026
- 0055 Docker basics: images, containers and the commands you need An image is a read-only package of an app and its dependencies, and a container is a running instance of it. Learn… Beginner · Explains an idea · Linux, macOS, Windows Article Docker basics 15 Oct 2026
- 0056 Docker Compose: run a small web app with a database Docker Compose describes several containers, their ports, volumes and environment in one compose.yaml file, then starts them all with docker compose up. Intermediate · Step by step · Linux, macOS, Windows Article Docker basics · Docker Compose 15 Oct 2026
- 0250 Containerize a Node.js app and run it with PostgreSQL using Docker Compose Package a small Node.js web app in a multi-stage Docker image, then run it next to PostgreSQL with Docker Compose, named volumes,… Intermediate · Guided project · Linux, macOS, Windows Article Docker basics · Docker Compose · PostgreSQL 15 Oct 2026
- 0268 What is a container? A container is a lightweight box that packs a program with everything it needs so it runs the same on any computer. Beginner · Explains an idea · Any system Article Docker basics 15 Oct 2026
Repos in the wild
- Watchtower (maintained fork): automatic container image updates nicholas-fedor/watchtower Watchtower watches your running containers, pulls newer versions of their images and restarts them with the same settings. This fork continues the project after the original containrrr repository was archived. GitHub
- Uptime Kuma: self-hosted uptime monitoring louislam/uptime-kuma Uptime Kuma is a self-hosted monitoring tool with a web UI that checks websites, TCP ports, DNS, Docker containers and more, draws uptime history and sends alerts. One container is all it needs. GitHub
- Trivy: scan container images, file systems and infrastructure code for known problems aquasecurity/trivy Trivy is an open source scanner from Aqua Security that finds known vulnerabilities, misconfigurations and exposed secrets in container images, project directories, Git repositories and Kubernetes clusters. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- Dominik +5,480 chars