Containerize a Python Web App with Docker
Build and run a Flask web app inside a Docker container from scratch.
Introduction
30 Second Summary
Every developer has hit the "works on my machine" problem. Code runs perfectly on one computer but breaks on another because of different software versions, missing dependencies, or configuration differences. Containers solve this by packaging an app and everything it needs into one portable unit that runs identically anywhere.
In this project, you will build a Python Flask web app, write a Dockerfile from scratch, and run your app inside a Docker container that you can access in your browser.
What You'll Build
A simple Python Flask web app that displays a personal greeting page, fully containerized with Docker using a Dockerfile you write from scratch.
By the end of this project, you'll have:
- A working Flask web app running inside a Docker container, accessible in your browser.
- A custom Docker image you built from a Dockerfile you wrote from scratch.
- Hands-on experience managing the container lifecycle (run, stop, rebuild, remove) using the Docker CLI.
- Secret Mission: Add a second route and use Docker volumes to enable live code reloading during development.
Are there any prerequisites?
You need Docker Desktop installed on your machine and a code editor (like VS Code). No Python experience is required. The app code is provided and the focus is entirely on Docker.
Verify Docker Installation
Before you containerize your own app, you need to confirm that Docker is ready to go on your machine. If the Docker engine is not running, every command in this project will fail with confusing errors.
In this step, you will verify the Docker CLI works, run a test container, and then clean it up. This proves the full pipeline is functional before you start building.
In this step, get ready to:
- Confirm Docker CLI responds to commands.
- Run a test container and view it in your browser.
- Stop and remove the test container.
Confirm Docker is running
The Docker CLI is your main tool for building images and managing containers. Two quick commands tell you whether the engine is alive and ready.
- In your terminal, check the installed Docker version by running this command:
docker --version
- Check that the Docker engine is running by listing active containers:
docker ps
What do these commands tell you?
`docker --version` prints the installed Docker version number. If this responds, the CLI is on your system path.
`docker ps` lists all running containers. If this responds without an error, the Docker engine (daemon) is active. You should see an empty table, or existing containers if you have used Docker before.
Seeing an error about the Docker daemon?
If you see a message mentioning that Docker cannot connect to the daemon, Docker Desktop is not running yet. Open Docker Desktop from your Start menu and wait for the whale icon in your taskbar to stop animating. Then try the commands again.
help me fix a Docker daemon connection error on Windows
Run your first container
Now that the engine is confirmed alive, you will pull and run a real container to prove the full pipeline works. Docker provides an official welcome image for exactly this purpose.
- Run Docker's welcome container by executing this command:
docker run -d -p 8080:80 docker/welcome-to-docker
What does this command do?
`docker run` creates and starts a new container from an image. Docker automatically downloads the image if it is not already on your machine.
- `-d` runs the container in detached mode (in the background), so your terminal stays free.
- `-p 8080:80` maps port 80 inside the container to port 8080 on your machine. This is called port mapping
- `docker/welcome-to-docker` is the name of the image to run.
- Open your browser and navigate to http://localhost:8080.
You should see Docker's welcome page confirming your container is running.
Don't see the welcome page?
Make sure you typed the port correctly in the URL. The command maps to port 8080 on your machine, not port 80.
If the page still does not load, check that no other application is using port 8080. You can try stopping other programs or changing the host port (e.g. -p 9090:80 and then visiting http://localhost:9090).
help me troubleshoot why Docker's welcome page is not loading in my browser
Clean up the test container
You proved Docker works. Now you will stop and remove the test container so it does not use resources while you build your own app.
- Find the running container's ID by running:
docker ps
You should see a table with one row. Note the value in the CONTAINER ID column (a short string like a1b2c3d4e5f6).
- Stop the running container by running this command:
docker stop [[CONTAINER_ID="your container ID from docker ps"]]
- Remove the stopped container by running:
docker rm [[CONTAINER_ID="your container ID from docker ps"]]
Why stop and remove separately?
`docker stop` gracefully shuts down the running process inside the container. `docker rm` deletes the container's filesystem and metadata from your machine.
A stopped container still takes up disk space until you remove it. Keeping cleanup in your workflow prevents old containers from piling up.
- Verify the container is gone by running:
docker ps -a
You should see an empty table (or only other containers if you have used Docker before). The welcome container is gone.
Getting an error when stopping or removing?
Double-check that you copied the correct container ID from the docker ps output. Container IDs are case-sensitive.
If you see a message saying the container is still running when you try to remove it, run docker stop first. You cannot remove a running container without stopping it.
help me fix a Docker container stop or remove error
Docker is confirmed working and your system is clean. Next up, you will create the Flask web app that you will containerize.
Create the Flask Application
Your Docker installation is confirmed and working. Now it's time to create the application you'll containerize.
You'll write a simple web app using Flask, a lightweight Python web framework. Docker doesn't care what language your app is written in. It just needs the code and a list of dependencies.
In this step, get ready to:
- Create app.py with a Flask web application.
- Create requirements.txt to declare the Flask dependency.
Create your project folder and write the Flask app
Every containerized application starts as regular code on your machine. You'll create a project folder, then write a small Flask app that serves an HTML greeting page.
- Switch back to your Windows Terminal (or PowerShell) and move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir docker-flask-app
cd docker-flask-app
- Confirm the folder was created by running:
ls
The folder is empty for now. You'll add files to it next.
- Open the docker-flask-app folder in VS Code by running:
code .
What if code is not recognized?
The VS Code installer on Windows adds code to your system PATH by default. If you see an error, open VS Code manually instead.
- Press the Windows key to open your search bar.
- Type Visual Studio Code and press Enter to open it.
- Click File in the top menu, then Open Folder.
- Navigate to your Desktop, select the docker-flask-app folder, and click Select Folder.
- In VS Code, create a new file called app.py by clicking the New File icon in the Explorer sidebar.
- Copy the following code into app.py:
from flask import Flask
# Create the Flask application instance
app = Flask(__name__)
# Define the home route that serves the greeting page
@app.route("/")
def home():
return """
<!DOCTYPE html>
<html>
<head>
<title>My Dockerized App</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; text-align: center; }
h1 { color: #2496ED; }
.container { background: #f0f8ff; padding: 30px; border-radius: 10px; }
</style>
</head>
<body>
<div class=\"container\">
<h1>Hello from Docker!</h1>
<p>This app is running inside a container.</p>
<p>Built with Flask and containerized with Docker.</p>
</div>
</body>
</html>
"""
# Run the app on all network interfaces so Docker can reach it
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
What does this code do?
- from flask import Flask imports the Flask framework so you can build a web app.
- app = Flask(__name__) creates the application instance that handles incoming web requests.
- @app.route("/") tells Flask to run the home() function whenever someone visits the root URL.
- host="0.0.0.0" makes the app listen on all network interfaces. This is required for Docker containers, because 127.0.0.1 would only be reachable from inside the container itself.
- Save app.py by pressing Ctrl+S (Windows) or Cmd+S (macOS).
- You should see app.py listed in the Explorer sidebar on the left.
Don't see app.py in the sidebar?
- Make sure you opened the docker-flask-app folder itself (not a parent folder). The Explorer sidebar should show DOCKER-FLASK-APP as the workspace name.
- Check that you saved the file. An unsaved file shows a dot on its tab.
Help me troubleshoot why my file isn't showing in VS Code's Explorer.
Pin Flask as a dependency
Docker needs to know which packages your app requires. A requirements.txt file tells pip (Python's package manager) exactly what to install inside the container.
- In VS Code, create a new file called requirements.txt in the same docker-flask-app folder.
- Add this single line to the file:
flask==3.1.3
Why pin a specific version?
The ==3.1.3 syntax locks Flask to an exact version. Without it, a future pip install flask might grab a newer version that behaves differently. Pinning guarantees your container builds the same way every time.
- Save requirements.txt.
- Switch to your terminal and confirm both files exist by running:
ls
You should see app.py and requirements.txt in the output.
Only see one file or none?
- Make sure your terminal is inside the docker-flask-app folder. Run pwd (PowerShell) to check your current directory.
- Confirm you saved both files in VS Code. Look for dots on unsaved tabs.
Help me figure out why my files aren't showing up when I run ls.
Understand how your files work together
You now have two files that fully describe your application. Together, they give Docker everything it needs to build a self-contained image.
How do these files connect to Docker?
Think of app.py as the "what" and requirements.txt as the "what it needs." In the next step, you'll write a Dockerfile that tells Docker how to combine these into a single portable package.
Docker will copy both files into a container, run pip install against requirements.txt to fetch Flask, then start app.py. The container includes everything. No Python install needed on the host.
✔️ Awesome, I've got everything!
Great. Make sure both files are saved and you'll be ready for the next step.
ⓧ I'd like to double check the full code
Here is the complete app.py:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return """
<!DOCTYPE html>
<html>
<head>
<title>My Dockerized App</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; text-align: center; }
h1 { color: #2496ED; }
.container { background: #f0f8ff; padding: 30px; border-radius: 10px; }
</style>
</head>
<body>
<div class=\"container\">
<h1>Hello from Docker!</h1>
<p>This app is running inside a container.</p>
<p>Built with Flask and containerized with Docker.</p>
</div>
</body>
</html>
"""
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
And the complete requirements.txt:
flask==3.1.3
Your application code is ready. Next up, you'll write a Dockerfile that teaches Docker how to package these files into a runnable container image.
Write Your Dockerfile
You have your Flask app and its dependencies listed out. Now you need to tell Docker how to package everything into a container image.
A Dockerfile is a text file with a set of instructions that Docker reads to build an image. Each instruction adds a layer to the image, creating a complete environment for your app to run in.
In this step, get ready to:
- Create a Dockerfile in your project folder.
- Write six instructions that package your Flask app into a container image.
- Understand how Docker layers and caching speed up rebuilds.
Create your Dockerfile
A Dockerfile tells Docker exactly what environment your app needs. It starts with a base image, which is a pre-built starting point that already includes an operating system and core tools.
- In your code editor, create a new file called Dockerfile inside your docker-flask-app folder.
- Make sure the file has no extension. It should be named exactly Dockerfile, not Dockerfile.txt or anything else.
What is a base image?
A base image is a pre-built environment that your image starts from. For this project, you'll use python:3.12-slim, which is a lightweight Linux environment with Python already installed.
The "slim" variant is Debian-based and smaller than the full image. It avoids compatibility issues that other minimal variants (like Alpine) can cause with certain Python packages.
Add your build instructions
Your Dockerfile needs six instructions. Each one tells Docker to do one thing during the build process.
- Paste the following code into your Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 5000
CMD ["python", "app.py"]
What does each instruction do?
- FROM python:3.12-slim sets the base image to a lightweight Linux environment with Python 3.12 pre-installed.
- WORKDIR /app creates a /app directory inside the container and makes it the active directory for all following instructions.
- COPY . . copies everything from your project folder on your machine into the container's /app directory.
- RUN pip install --no-cache-dir -r requirements.txt installs Flask inside the container. The --no-cache-dir flag keeps the image smaller by skipping pip's download cache.
- EXPOSE 5000 documents that the app listens on port 5000. This doesn't actually publish the port. It serves as documentation for anyone reading the Dockerfile.
- CMD ["python", "app.py"] sets the command that runs when the container starts. This launches your Flask app.
- Save Dockerfile.
- Confirm the file exists by running this in your terminal:
ls
You should see Dockerfile, app.py, and requirements.txt listed in the output.
Don't see Dockerfile in the output?
- Make sure you're in the docker-flask-app folder. Run cd docker-flask-app if you're one level up.
- Check that the file isn't named Dockerfile.txt. Windows can add a hidden .txt extension. In File Explorer, enable **View > File name extensions** to check.
- Confirm you saved the file after pasting the instructions.
Help me troubleshoot why my Dockerfile isn't showing up in the directory listing.
✔️ Awesome, I've got everything!
Great. Double check you've saved your Dockerfile and you're ready for the next substep.
ⓧ I'd like to double check the full code
Here is the complete Dockerfile for comparison:
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 5000
CMD ["python", "app.py"]
Understand how Docker layers work
Each instruction in your Dockerfile creates a "layer" in the image. Docker caches these layers and reuses unchanged ones on subsequent builds.
Why do layers matter?
When you rebuild your image after changing only app.py, Docker reuses the cached layers for FROM, WORKDIR, and RUN pip install. It only re-executes the layers that changed.
This makes rebuilds much faster because Docker skips expensive operations like downloading and installing packages. You'll see this caching in action in the next step when you build and modify your image.
Your Dockerfile is ready. Next up, you'll build this file into a Docker image and run your first container.
Build and Run Your Container
Your Dockerfile is written and your application files are ready. Now it's time to bring everything together by building a Docker image and launching your first container.
Docker will read your Dockerfile, execute each instruction layer by layer, and package everything into a portable image. You'll then run that image as a container and see your app live in the browser.
In this step, get ready to:
- Build a Docker image from your Dockerfile.
- Run a container and access your app in the browser.
- Inspect a running container using Docker CLI commands.
Build your Docker image
Your Dockerfile contains the instructions. The docker build command reads those instructions and produces an image you can run anywhere.
- Build your Docker image by running this command from inside the docker-flask-app folder:
docker build -t flask-app .
What does this command do?
docker build reads your Dockerfile and executes each instruction to create an image.
The -t flask-app flag tags the image with the name flask-app so you can refer to it easily later. The . at the end tells Docker to use the current folder as the build context (where to find your files).
- Watch the terminal output as Docker downloads the python:3.12-slim base image, copies your files, and installs Flask.
- The build is complete when you see a line that includes naming to docker.io/library/flask-app near the end of the output.
Build failed or stuck downloading?
- Make sure Docker Desktop is running. If it's not, the CLI cannot communicate with the Docker engine.
- Confirm you are inside the docker-flask-app folder. If Docker cannot find your Dockerfile, the build will fail immediately.
- Check your internet connection. The first build downloads the base image, which requires network access.
Why is my Docker build failing?
Run your container
An image is a blueprint. A container is a running instance of that blueprint. Let's start one up.
- Start a container from your image by running:
docker run -d -p 5000:5000 --name my-flask-app flask-app
What do these flags mean?
- -d runs the container in detached mode (in the background), so you get your terminal back.
- -p 5000:5000 maps port 5000 on your machine to port 5000 inside the container. Without this, the app would be unreachable.
- --name my-flask-app gives the container a human-readable name so you can refer to it easily.
- flask-app at the end tells Docker which image to use.
- You should see a long string of characters printed in the terminal. This is your container ID, confirming the container has started.
- Open your browser and navigate to http://localhost:5000.
- You should see a blue heading that says "Hello from Docker!" with two lines of text below it, all inside a light blue box.
Don't see your greeting page?
- If the browser says the site can't be reached, wait a few seconds and refresh. The container may still be starting up.
- If you see a "port already in use" error in the terminal, another process is using port 5000. Stop it or change the host port to something else like -p 5001:5000 and visit http://localhost:5001 instead.
- Make sure the docker build step completed without errors. If the image wasn't built correctly, the container won't start.
Why can't I see my app at localhost:5000?
Inspect your running container
Your container is running in the background. Docker gives you several commands to peek inside and see what's happening.
- List all running containers by running:
docker ps
- You should see a table with one row showing your my-flask-app container, its status, the port mapping, and the image it was created from.
- View the application output by running:
docker logs my-flask-app
What do these logs show?
Flask prints a startup message to standard output when the app begins serving requests. The docker logs command captures everything the container's main process writes to standard output and standard error.
- Open an interactive shell inside your running container by running:
docker exec -it my-flask-app sh
What is happening here?
This command opens a shell session inside the container. You're now exploring the Linux filesystem that Docker created for your app.
Try typing ls to see your project files, or python --version to confirm Python is installed inside the container.
- Type exit and press Enter to leave the container shell and return to your normal terminal.
Shell command not responding?
- If you see an error that the container is not running, go back and run docker ps to check its status. It may have stopped due to a crash in your app code.
- If the terminal looks frozen, make sure you included both -i and -t flags. Without them, the shell session won't be interactive.
Why isn't docker exec working on my container?
Your app is running inside a container and accessible in the browser. Next up, you'll modify your app, rebuild the image, and learn how to manage the full container lifecycle.
Modify, Rebuild, and Manage Containers
Your Flask app is running in a Docker container, accessible in your browser. That's the core Docker workflow: write code, build an image, run a container.
In a real project, your code changes constantly. You need to know how to update your app, rebuild the image, and replace the running container with the new version.
In this step, get ready to:
- Modify your app code and rebuild the Docker image.
- Replace a running container with an updated version.
- Clean up all Docker resources (containers and images).
Update your app and rebuild the image
When you change application code, the running container doesn't automatically update. You need to rebuild the image and start a fresh container from it.
- In your code editor, open app.py from the docker-flask-app folder.
- Find this line inside the HTML:
<h1>Hello from Docker!</h1>
- Change the text between the <h1> tags to any greeting you like. For example:
<h1>Hello from Docker! I just updated this app.</h1>
- Save app.py.
Your file is saved, but the running container still serves the old message. To get your changes into a container, you need to rebuild the image first.
- Rebuild your Docker image by running this command in Windows Terminal:
docker build -t flask-app .
What's different about this rebuild?
Watch the output closely. Docker reuses cached layers for instructions that haven't changed (like FROM and RUN pip install). Only the COPY . . layer and everything after it gets rebuilt, because that's where your file changed.
This layer caching is what makes Docker rebuilds fast. Unchanged steps are skipped entirely.
- You should see output showing cached layers for the early steps and a fresh build for the later steps.
Build failing?
Make sure you're still inside the docker-flask-app folder in your terminal. Docker looks for the Dockerfile in the current directory (that's what the . at the end means).
Also check that your edit to app.py didn't accidentally break the Python string (make sure the triple quotes """ are still intact).
Help me fix my Docker build error.
Replace the running container with your updated image
Your image is rebuilt, but the old container is still running the previous version. You can't run a new container with the same name and port while the old one exists.
- Stop and remove the old container, then start a new one by running these commands:
docker stop my-flask-app
docker rm my-flask-app
docker run -d -p 5000:5000 --name my-flask-app flask-app
What do these three commands do?
- docker stop my-flask-app gracefully shuts down the running container.
- docker rm my-flask-app removes the stopped container entirely, freeing up the name.
- docker run -d -p 5000:5000 --name my-flask-app flask-app starts a brand new container from the freshly rebuilt image.
This stop-remove-run cycle is how you deploy code changes when working with Docker containers.
- Open http://localhost:5000 in your browser (or refresh if it's still open).
- You should see your updated greeting message displayed on the page.
Still seeing the old message?
Try a hard refresh in your browser (Ctrl+Shift+R) to bypass the cache. If you still see the old message, run docker ps to confirm the new container is running.
If no container shows up, check that you ran all three commands (stop, remove, then run) in order.
Why am I still seeing the old version of my app?
Clean up all Docker resources
Unused containers and images take up disk space over time. Cleaning up after yourself is a good Docker habit.
- Stop and remove your container, then remove the image by running these commands:
docker stop my-flask-app
docker rm my-flask-app
docker rmi flask-app
What does docker rmi do?
The first two commands (docker stop and docker rm) handle the container. The third command, docker rmi flask-app, removes the image itself from your machine.
You must stop and remove all containers using an image before you can delete that image.
- Verify everything is cleaned up by running:
docker ps -a
- You should NOT see my-flask-app in the output. This confirms the container is gone.
- Now check your images by running:
docker images
- You should NOT see flask-app listed. Your Docker environment is clean.
Seeing an error when removing the image?
If you see a message about the image being used by a container, you likely skipped the docker rm step. Run docker rm my-flask-app first, then try docker rmi flask-app again.
Help me clean up my Docker containers and images.
Your files are still safe
Removing containers and images only affects Docker. Your app.py, requirements.txt, and Dockerfile are still on your machine in the docker-flask-app folder. You can rebuild the image any time with a single command.
Secret mission
Live Reloading with Volumes
You've learned the build-stop-remove-rebuild cycle. But what if you could skip that loop during development? Add a new route to your Flask app and use Docker volumes to see changes instantly without rebuilding your image.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on your local machine using Docker Desktop, so there are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Docker container my-flask-app (running, serving your web app on port 5000).
- Docker image flask-app (your custom-built image).
- Downloaded base image python:3.12-slim and the docker/welcome-to-docker image from Step 1.
- Local project folder docker-flask-app on your Desktop.
Keep everything running
No action needed. Choose this if you are still actively building or experimenting with your Flask app and Docker setup.
- Your container my-flask-app continues running and serving on http://localhost:5000.
- Docker Desktop manages the container in the background.
- Your project files remain in the docker-flask-app folder on your Desktop.
Pause - I'll come back to this later
Stop the running container to free up resources. Your image and files stay intact so you can resume later.
- Stop the running container by running:
- When you are ready to resume, start it again with:
docker stop my-flask-app
To restart later, run:
docker start my-flask-app
Your app will be available at http://localhost:5000 again.
Delete - I don't want to use this again
Remove all project resources and start fresh. This frees up disk space used by Docker images and your local files.
- Stop and remove the container by running:
docker stop my-flask-app
docker rm my-flask-app
- Remove the Docker images you built and downloaded by running:
docker rmi flask-app
docker rmi python:3.12-slim
docker rmi docker/welcome-to-docker
- Verify the images are gone by running:
docker images
- Delete the local project folder. On Windows (PowerShell), run:
Remove-Item -Recurse -Force ~/Desktop/docker-flask-app
- On macOS or Linux, run:
rm -rf ~/Desktop/docker-flask-app
- Confirm the folder is gone by checking your Desktop.
Nice Work!
Nice Work!
You just containerized your first application from scratch. You now have a working Flask web app running inside a Docker container, built from a Dockerfile you wrote yourself.
You've learned how to:
- Created a Dockerfile from scratch with six instructions (FROM, WORKDIR, COPY, RUN, EXPOSE, CMD) and built a custom Docker image using docker build.
- Ran, inspected, stopped, and removed containers using the Docker CLI to manage the full container lifecycle.
- Understood how Docker layers and caching make rebuilds fast by reusing unchanged instructions.
- Used Docker volumes for live code reloading and added a second route to your Flask app.
Ready to quiz yourself?