Containerize a Web App with Docker
Write a Dockerfile from scratch and run a Flask app in a container.
Introduction
30 Second Summary
Every developer has heard "it works on my machine." Docker solves that by packaging your app and everything it needs into a single, portable container that runs the same way everywhere.
In this project, you will write a Dockerfile from scratch, build a Docker image, and run a container that serves a web page you can visit in your browser.
What You'll Build
You will type http://localhost:5000 into your browser and see a live web page served from inside a Docker container you built yourself.
By the end of this project, you'll have:
- A Dockerfile you wrote from scratch that packages a Python web app into a portable image you can run anywhere.
- A running container serving a live web page you can visit and refresh in your browser.
- Firsthand proof of container isolation: you will watch your app refuse connections until you explicitly open the port with port mapping.
- Secret Mission: Customize your app's greeting at runtime using environment variables, without rebuilding the image.
Are there any prerequisites?
You need Docker Desktop and VS Code installed on your machine. No programming experience or cloud accounts are required.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters. You're about to containerize a Python web app using Docker, and understanding the full picture upfront makes every step click.
Set Up Your Project Files
Before you can build a Docker image, you need something to put inside it. A Docker image packages your application along with everything it needs to run.
In this step, you will create a tiny Flask web app and a requirements file that tells Docker what to install. You will also prepare an empty Dockerfile for the next step.
In this step, get ready to:
- Verify Docker Desktop is running from the terminal.
- Create a project folder with three files.
- Write the Flask web app and its dependency list.
Open a terminal and verify Docker is running
You need a terminal inside VS Code to run Docker commands. The terminal is where you will build images and start containers throughout this project.
- Open the built-in terminal in VS Code by pressing Ctrl+`.
What do you think you will see when you ask Docker for its version? Take a guess before running the command.
- Check that Docker is ready by running this command:
docker version
You should see version information for both a Client section and a Server section. This confirms Docker Desktop is running and your terminal can communicate with it.
Seeing 'command not found' or 'Cannot connect to the Docker daemon'?
Make sure Docker Desktop is open and fully started. Look for the Docker whale icon in your system tray (Windows) or menu bar (macOS). It should show a steady state, not "starting."
If the icon is missing, launch Docker Desktop and wait for it to finish initializing, then try the command again.
help me fix Docker not responding to commands in my terminal
Create your project folder and files
Every Docker project starts with a folder containing your source code, a dependency list, and a Dockerfile. You will create all three files now and fill them in shortly.
- 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
- Create the three files your project needs by running the command for your operating system:
macOS
touch app.py requirements.txt Dockerfile
Windows
New-Item -ItemType File app.py
New-Item -ItemType File requirements.txt
New-Item -ItemType File Dockerfile
- Confirm the files exist by running:
ls
You should see app.py, requirements.txt, and Dockerfile listed in the output.
Don't see all three files?
Make sure you ran the file-creation command inside the docker-flask-app folder. Run pwd (macOS) or Get-Location (Windows) to check your current directory ends with docker-flask-app.
help me find where my project files were created
Write the Flask app code
Your app is a minimal Python web server. When someone visits the homepage, it returns an HTML response. Flask handles all the networking details so the code stays short.
- Open app.py in VS Code by clicking it in the Explorer sidebar, then paste the following code:
from flask import Flask
# Create the Flask application
app = Flask(__name__)
# Define the home page route
@app.route("/")
def home():
return "<h1>Hello from Docker!</h1><p>This app is running inside a container.</p>"
# Run the app on all network interfaces, port 5000
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
What does this code do?
- app = Flask(__name__) creates a new Flask web application.
- @app.route("/") tells Flask to call the home() function whenever someone visits the root URL.
- host="0.0.0.0" makes the app listen on all network interfaces, not just localhost. This is critical for Docker because without it, the app would only be reachable from inside the container itself.
- port=5000 is the port number your app will listen on inside the container.
- Save app.py by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Open requirements.txt in the Explorer sidebar, then paste this single line:
flask==3.1.3
Why pin the version?
Pinning flask==3.1.3 ensures every build of your Docker image installs the exact same version of Flask. Without a pin, a future build could pull a newer version that behaves differently.
- Save requirements.txt.
Your Dockerfile stays empty for now. You will write it line by line in the next step.
✔️ Awesome, I've got everything!
Great. Double check you have saved both files before moving on.
ⓧ 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 "<h1>Hello from Docker!</h1><p>This app is running inside a container.</p>"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
And here is requirements.txt:
flask==3.1.3
VS Code not showing your files in the sidebar?
If the Explorer sidebar is empty, you may need to open the project folder in VS Code. Click File in the top menu bar, then Open Folder, and select the docker-flask-app folder on your Desktop.
help me open my project folder in VS Code so I can see my files
Your project files are ready. Next up, you will write the Dockerfile that tells Docker how to package this app into a portable image.
Write Your Dockerfile and Build the Image
Your Flask app and its dependency file are ready. The next step is telling Docker exactly how to package them into a portable image.
A Dockerfile is a recipe. Each line is an instruction that tells Docker what base operating system to use, what packages to install, and how to start your app. Without it, Docker has nothing to work with.
In this step, get ready to:
- Write a complete Dockerfile that packages your Flask app into an image.
- Build the image and confirm it appears in your local image list.
Write the Dockerfile
Each instruction in a Dockerfile runs top-to-bottom. Docker executes them in order to assemble your image layer by layer.
- Open the empty Dockerfile in VS Code and paste the following code:
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
What does each instruction do?
- FROM python:3.13-slim sets the base image. The slim variant is much smaller than the full Python image, so your builds are faster.
- WORKDIR /app creates and moves into the /app directory inside the container. All following commands run from here.
- COPY requirements.txt . copies just the dependency list into the container first. RUN pip install then installs those packages. Separating this from the app code means Docker can cache the install layer and skip it when only your code changes.
- COPY . . copies the rest of your project files. EXPOSE 5000 documents the port your app listens on (it does not publish the port by itself). CMD ["python", "app.py"] sets the command that runs when a container starts from this image.
- Save Dockerfile.
Build failing later? Check the filename
The file must be named exactly Dockerfile with a capital D and no file extension. If VS Code added .txt or you used a lowercase d, Docker will not find it.
Also confirm there is no dot at the start of the filename (.Dockerfile is a different thing).
ask me for help
✔️ Awesome, I've got everything!
Great. Double check you have saved the file, then move on to building the image.
ⓧ I'd like to double check the full code
Your Dockerfile should look exactly like this:
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
Build the image
With the recipe written, you can now ask Docker to execute it and produce a reusable image.
- Build your Docker image by running this command in your VS Code terminal (make sure you are inside your project folder):
docker build -t my-flask-app .
Docker reads each instruction and executes it in sequence. You will see output for each step as it downloads the base image, installs Flask, and copies your files.
What does this command do?
- -t my-flask-app names (tags) the image so you can refer to it later. Without this, Docker assigns a random ID.
- . (the dot at the end) tells Docker to use the current directory as the build context. Docker looks for a file named Dockerfile in this directory.
Seeing an error during the build?
If you see a message about the Dockerfile not being found, confirm you are in the project folder that contains the Dockerfile. Run ls (macOS/Linux) or dir (Windows) to check.
If you see a syntax error mentioning a specific line, open Dockerfile and compare it character-for-character against the code above. A missing space or wrong capitalisation can break the build.
If Docker cannot connect to the internet to download the base image, make sure Docker Desktop is running (check for the whale icon in your system tray).
ask me for help
Verify the image exists
The build finished. Before moving on, predict what you expect to see when you list your local images.
- Check your image is saved locally by running:
docker image ls
You should see my-flask-app in the list with the tag latest. This confirms Docker successfully built your image and stored it on your machine.
Don't see my-flask-app in the list?
If the build command showed errors, the image was not created. Scroll up in your terminal to find the error message and fix the Dockerfile issue, then run docker build -t my-flask-app . again.
ask me for help
Your image is built and stored locally. Next up, you will run it as a container and discover why your browser cannot reach it.
Run Your Container and Hit the Wall
Your image is built and tagged. The next move is to turn it into a running container and visit your app in the browser.
Docker containers run in their own isolated environment, separate from your host machine. In this step, you will experience what that isolation looks like when you try to reach a running app from outside its container.
In this step, get ready to:
- Start your container and see Flask running inside it.
- Try to visit your app at localhost:5000 in the browser.
- Discover what container isolation means firsthand.
Run the container from your image
You have a Docker image. Now you will create a live container from it. A container is a running instance of an image.
- Start your container by running this command in your VS Code terminal:
docker run my-flask-app
What does this command do?
docker run creates a new container from the specified image and starts it immediately.
The container runs in the foreground, so all output from your Flask app prints directly in your terminal. You will see Flask start up in real time.
You should see Flask's startup output in your terminal, including a line that contains Running on http://0.0.0.0:5000. Flask is running inside the container.
Seeing an error instead of Flask output?
- Make sure Docker Desktop is still running. Look for the Docker whale icon in your system tray (Windows) or menu bar (macOS).
- Confirm the image name is exactly my-flask-app with no typos. Run docker image ls to double-check.
- Why is my docker run command failing?
Visit your app in the browser
Flask says it is running on port 5000. Before you open your browser, what do you think will happen when you visit http://localhost:5000?
- Open your browser and navigate to http://localhost:5000.
The page does not load. Your browser shows a message like "This site can't be reached" or "Connection refused."
This is not a bug. This is container isolation working exactly as designed.
What is container isolation?
Your container runs in its own isolated network, completely separate from your host machine. Even though Flask is running on port 5000 inside the container, that port is not accessible from outside.
The EXPOSE 5000 line in your Dockerfile only documents the port. It does not actually publish it to your host machine. To bridge the gap between your host and the container's network, you need port mapping.
Stop the container
Your container is still running in the foreground, tied to your terminal. You need to stop it before moving on.
- Switch back to your VS Code terminal where the Flask output is running.
- Press Ctrl+C to stop the container.
Flask's output stops and your normal terminal prompt returns. The container is no longer running.
You just saw container isolation in action. Your app was running perfectly inside its own world, but nothing could reach it from outside. In the next step, you will fix this with a single flag that maps the container's port to your host machine.
Fix It with Port Mapping
In the last step, you proved that Docker containers are isolated. Flask was running happily inside the container, but your browser had no way to reach it.
Port mapping solves this. The -p flag tells Docker to forward traffic from a port on your host machine to a port inside the container, creating a tunnel through the isolation.
In this step, get ready to:
- Map a host port to the container port and access your app in the browser.
- Run your container in detached mode so it stays alive in the background.
Run the container with port mapping
Last time you ran docker run my-flask-app without any port flag. This time, you will add -p to bridge the gap between your machine and the container.
- Start the container with port mapping by running this command:
docker run -p 5000:5000 my-flask-app
What does -p 5000:5000 mean?
- The number before the colon is the host port (your machine).
- The number after the colon is the container port (where Flask is listening inside the container).
- Traffic hitting localhost:5000 on your machine gets forwarded into the container's port 5000.
You should see Flask's startup message in the terminal, just like before.
Before you open your browser, what do you think will happen when you visit http://localhost:5000 this time?
- Open your browser.
- Visit http://localhost:5000 in the address bar.
You should see the "Hello from Docker!" heading and "This app is running inside a container." below it.
Why did it work this time?
The EXPOSE 5000 instruction in your Dockerfile only documents which port the container listens on. It does not actually publish the port.
The -p flag at runtime is what actually creates the network tunnel. Without it, the container's ports stay sealed off from the outside world.
Page still not loading?
- Make sure you see Flask's Running on http://0.0.0.0:5000 message in your terminal. If the container didn't start, check for typos in the command.
- If you see "address already in use", another process is occupying port 5000. Try docker run -p 5001:5000 my-flask-app and visit http://localhost:5001 instead.
- Help me debug why my browser can't reach my Docker container with port mapping.
Run in detached mode
Right now, Flask's output is taking over your terminal. You can't type any other commands. Detached mode lets the container run in the background so you get your terminal back.
- Press Ctrl+C in your terminal to stop the running container.
- Run the container again in detached mode by adding the -d flag:
docker run -d -p 5000:5000 my-flask-app
What does -d do?
The -d flag runs the container in the background (detached mode). Docker prints the container ID and immediately returns you to the terminal prompt.
The container keeps running until you explicitly stop it or restart your machine.
Docker prints a long string of characters. That's your container ID.
- Confirm the container is running by checking Docker's process list:
docker ps
You should see your my-flask-app container listed with its port mapping shown as 0.0.0.0:5000->5000/tcp in the PORTS column.
Container not showing in docker ps?
- If docker ps shows nothing, the container may have exited immediately. Run docker ps -a to see all containers including stopped ones. Check the STATUS column for clues.
- If the status shows "Exited", there may be a port conflict. Stop any leftover containers using port 5000 first with docker stop followed by the container ID from docker ps -a.
- Help me figure out why my Docker container exited immediately in detached mode.
Your container is running in the background. Do you think your app is still accessible in the browser without Flask's output visible in the terminal?
- Open your browser and visit http://localhost:5000.
You see the same "Hello from Docker!" heading and "This app is running inside a container." message. Your container is serving requests independently in the background without tying up your terminal.
Secret mission
Customize Your App with Environment Variables
Your app shows a hardcoded message baked into the image. What if you wanted to change it without rebuilding? Use environment variables to configure your container at runtime, the same way production apps handle configuration.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally using Docker Desktop, so there are no ongoing cloud costs. Decide whether to keep your containers running, stop them to free up RAM, or delete everything entirely.
Resources you used:
- Docker containers (two running on ports 5000 and 5001, using RAM while active).
- Docker image my-flask-app:latest (stored locally, uses disk space).
- Project folder on your Desktop (containing app.py, requirements.txt, Dockerfile).
Keep everything running
No action needed. Choose this if you are still actively building or experimenting with your containers.
- Your two containers will keep running in the background until you stop them or restart your machine.
- Docker Desktop will continue using RAM while containers are active.
- You can revisit your app at any time by opening http://localhost:5000 or http://localhost:5001 in your browser.
Pause - I'll come back to this later
Stop your running containers to free up RAM, but keep your image and project files so you can start again quickly later.
- List your running containers by running:
docker ps
- Copy the container ID for each running container from the output, then stop each one by running:
docker stop <container-id>
- Repeat for the second container.
- Your my-flask-app image stays on disk. Next time you want to use it, run docker run -d -p 5000:5000 my-flask-app to start a fresh container.
Delete - I don't want to use this again
Remove all Docker resources and project files to start completely fresh.
- Stop all running containers by running:
docker stop $(docker ps -q)
Windows users
The command above works in PowerShell. If you use Command Prompt (CMD) instead, open PowerShell for this step.
- Remove all unused images, stopped containers, and build cache by running:
docker system prune -a
- Docker will ask you to confirm. Type y and press Enter.
- Delete your project folder from the Desktop. On macOS, open Finder, navigate to your Desktop, right-click the project folder, and select Move to Trash. On Windows, open File Explorer, navigate to your Desktop, right-click the project folder, and select Delete.
Nice Work!
Nice Work!
You just containerized a web application from scratch using Docker. You wrote every line of the Dockerfile yourself, built an image, hit the isolation wall, and fixed it with port mapping.
You've learned how to:
- Write a Dockerfile from scratch using FROM, WORKDIR, COPY, RUN, EXPOSE, and CMD to package a Python app into a portable image.
- Discover container isolation firsthand when your app was unreachable, then fix it with -p port mapping to connect the outside world to your container.
- Run containers in detached mode with docker run -d and verify them with docker ps.
- (Secret Mission) Use docker run -e to pass environment variables and customize your app at runtime without rebuilding the image.
Ready to quiz yourself?