Containerize a Web App with Docker
Build and ship a multi-container Python web app using Docker and Docker Compose.
Introduction
30 Second Summary
Every time you install an app on your computer, you deal with version conflicts, missing libraries, and the classic "it works on my machine" problem. Containers solve this by packaging an app with everything it needs to run, so it behaves the same on any machine.
In this project, you will containerize a Python web application with Docker, connect it to a Redis database using Docker Compose, and publish your finished image to Docker Hub for anyone to download and run.
What You'll Build
A page-visit counter web application that uses Flask as the web server and Redis as the database to track how many times users have visited the page.
By the end of this project, you'll have:
- A working Dockerfile that containerizes a Python Flask web app into a portable image.
- A compose.yaml file that orchestrates the Flask app and Redis containers together as a multi-container application.
- A published image on Docker Hub that anyone can pull and run with a single command.
- Secret Mission: Add a /stats endpoint that shows uptime, total requests, and the current server time.
Are there any prerequisites?
You should have basic comfort with using a terminal or command line. No prior Docker or Python experience is needed.
Verify Docker and Create a Docker Hub Account
Before writing any code, you need to confirm that Docker is working correctly on your machine.
You also need a Docker Hub account so you can publish your finished image for anyone to download and run.
In this step, get ready to:
- Verify Docker Desktop is installed and running.
- Create a Docker Hub account and log in from your terminal.
- Create and open your project folder in VS Code.
Confirm Docker Desktop is running
Docker Desktop needs to be running in the background for any docker commands to work in your terminal.
- Press the Windows key (Windows) or Cmd+Space (macOS) to open your search bar.
- Type PowerShell (Windows) or Terminal (macOS) and press Enter to open it.
- Check your Docker installation by running this command:
docker --version
✔️ I see a version number
Docker is installed and ready to go.
ⓧ Command not found or error
Make sure Docker Desktop is open and fully started before running the command again.
- Open Docker Desktop from your applications or system tray.
- Wait for the Docker whale icon in your system tray to stop animating (this means it has finished starting).
- Try running docker --version again.
- Verify Docker can pull images and run containers by running this command:
docker run hello-world
What does this command do?
docker run hello-world downloads a tiny test image from Docker Hub and runs it inside a container.
The container prints a confirmation message and then exits. If you see the message, it proves Docker can pull images from the internet and execute containers on your machine.
You should see a message that says "Hello from Docker! This message shows that your installation appears to be working correctly." followed by a numbered explanation of what Docker just did.
Not seeing the hello-world message?
- Confirm Docker Desktop is fully started. The whale icon in your system tray should be steady, not animating.
- Close your terminal, reopen it, and run the command again. Docker Desktop sometimes needs a moment to register its CLI tools.
Help me troubleshoot why docker run hello-world isn't working
Create a Docker Hub account and log in
Docker Hub is a public registry where you store and share container images. You need an account to push your finished image later in this project.
- Open https://hub.docker.com in your browser.
- Click Sign Up and create a free account.
- Choose a username you'll remember. You will use this username when tagging and pushing images.
- Complete the sign-up process and verify your email if prompted.
What is Docker Hub?
Think of Docker Hub like GitHub, but for container images instead of source code. Anyone can pull your public images and run them on their own machine without installing your app's dependencies.
- Switch back to your terminal and log in to Docker Hub by running this command:
docker login
What will I see?
Docker will prompt you for your Docker Hub username and password. Type each one and press Enter. Your password won't appear on screen as you type it.
You should see a message confirming you have logged in successfully.
Login failed?
- Double-check that you're entering your Docker Hub username (not your email) when prompted.
- If you just created your account, make sure you verified your email before trying to log in.
- If you use a password manager, try copying your password directly to avoid typos.
Help me fix my docker login issue
Create the project folder and open it in VS Code
You need a dedicated folder for your project files. All of your Docker configuration and application code will live here.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir docker-visit-counter
cd docker-visit-counter
- Open the folder in VS Code by running:
code .
VS Code should open with the docker-visit-counter folder shown in the Explorer sidebar on the left. The folder will be empty for now.
VS Code didn't open?
- On Windows, the code command is usually added to your PATH automatically when VS Code is installed. Try closing and reopening your terminal, then run code . again.
- On macOS, you may need to install the shell command first. Open VS Code manually, press Cmd+Shift+P, type Shell Command: Install, and select the option. Then retry code . in your terminal.
Help me get the code command working in my terminal
Your environment is ready. Next up, you'll pull your first container image and learn the core Docker commands for running and managing containers.
Run Your First Containers
You have Docker running and your project folder ready. Now it's time to see containers in action.
Before writing your own Dockerfile, you'll pull a pre-built container image from Docker Hub and run it locally. This gives you hands-on practice with the core Docker lifecycle: pulling, running, stopping, and removing containers.
In this step, get ready to:
- Pull and run a pre-built Nginx web server container.
- Manage running containers with Docker commands.
- Clean up stopped containers and images.
Pull and run an Nginx container
Nginx is a popular web server. Docker Hub hosts an official Nginx image that you can download and run in a single command.
- Pull and run an Nginx container with port 8080 on your machine mapped to port 80 inside the container by running this command:
docker run -d -p 8080:80 nginx
What does this command do?
- -d runs the container in detached mode (in the background), so you get your terminal back.
- -p 8080:80 maps port 8080 on your machine to port 80 inside the container. You access the server at port 8080, and Docker routes traffic to port 80 where Nginx listens.
- nginx is the image name. Docker automatically pulls it from Docker Hub if it's not already on your machine.
- Open http://localhost:8080 in your browser.
- You should see the Nginx welcome page with the heading "Welcome to nginx!" confirming the web server is running inside your container.
Don't see the welcome page?
- Make sure Docker Desktop is running. The Docker engine must be active for containers to start.
- Check if another application is already using port 8080. Try stopping that application or change the port to 9090:80 instead.
- Run docker ps to check if the container is actually running. If it's not listed, the container may have exited immediately.
Why isn't my Nginx container showing the welcome page?
Explore container management commands
Your Nginx container is running in the background. You need commands to inspect and control running containers.
- See all running containers by running this command:
docker ps
You should see a table with your Nginx container listed, showing its container ID, image name, status, and port mapping.
What does the output show?
Each row in the docker ps output represents one running container. The CONTAINER ID is a short alphanumeric string you'll use to target this container in future commands. The PORTS column confirms your port mapping is active.
- Copy the container ID from the output (the first column).
- Stop the running container by running this command:
docker stop [[CONTAINER_ID="your container ID from docker ps"]]
- Confirm the container has stopped by listing all containers, including stopped ones:
docker ps -a
You should see your Nginx container listed with a status of "Exited" instead of "Up".
What does the -a flag do?
By default, docker ps only shows running containers. The -a flag shows all containers on your system, including stopped ones. A stopped container still exists on disk until you explicitly remove it.
Container won't stop?
- Double-check that you copied the correct container ID from docker ps. You only need the first few characters.
- If the container seems stuck, try docker kill followed by the container ID to force-stop it.
Help me stop my Docker container.
Clean up the container and image
Stopped containers and downloaded images take up disk space. Cleaning them up is good practice after you're done experimenting.
- Remove the stopped Nginx container by running this command:
docker rm [[CONTAINER_ID="your container ID from docker ps -a"]]
- Remove the Nginx image from your machine by running this command:
docker rmi nginx
- Confirm the image has been removed by running:
docker images
You should see that nginx is no longer in the list. Your system is back to a clean state.
Seeing an error when removing?
- If docker rmi nginx says the image is in use, you still have a container using it. Run docker rm on the container first, then try docker rmi again.
- If docker rm says the container is still running, stop it with docker stop first.
Help me remove my Docker containers and images.
What is the container lifecycle?
You just walked through the full lifecycle of a container: pull an image, run it as a container, stop the container, remove the container, and remove the image. These five commands (docker run, docker ps, docker stop, docker rm, docker rmi) are the foundation of every Docker workflow.
Now you know how to run and manage existing containers. Next, you'll write your own Python app and package it into a custom Docker image using a Dockerfile.
Write Your Python App and Dockerfile
In the previous step, you pulled and ran pre-built containers from Docker Hub. You saw how Docker downloads images and runs them as isolated containers.
Now it's time to create your own application and package it into a custom image. You'll write a Python web app that counts page visits, then create a Dockerfile that tells Docker exactly how to build it into a portable image.
In this step, get ready to:
- Create a Flask web application that tracks page visits using Redis.
- Write a Dockerfile to containerize the application.
- Add a .dockerignore file to keep the image lean and secure.
Create the Flask app
Your visit counter app uses Flask as the web server and Redis as the database to persist the count. Every time someone visits the page, the app increments a counter stored in Redis and displays the total.
- In VS Code, create a new file by pressing Ctrl+N.
- Save it immediately with Ctrl+S and name it app.py inside your docker-visit-counter folder.
- Paste the following code into app.py:
import os
from flask import Flask
import redis
app = Flask(__name__)
cache = redis.Redis(host=os.environ.get("REDIS_HOST", "redis"), port=6379)
@app.route("/")
def hello():
count = cache.incr("visits")
return f"<h1>Hello from Docker!</h1><p>This page has been visited <b>{count}</b> time(s).</p>"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
What does this code do?
This is your entire web application in one file. Here's what each section does:
- cache = redis.Redis(...) creates a connection to a Redis database. The REDIS_HOST environment variable lets Docker Compose tell the app where Redis lives.
- cache.incr("visits") increments the visits key in Redis by 1 every time someone loads the page, then returns the new count.
- app.run(host="0.0.0.0", port=5000) makes Flask listen on all network interfaces inside the container so Docker can forward traffic to it.
- Save app.py with Ctrl+S.
You won't see a change yet because the app needs Redis to run. The next file declares the packages Python needs to install.
- Create another new file with Ctrl+N and save it as requirements.txt in the same folder.
- Paste the following into requirements.txt:
flask==3.1.3
redis==7.4.0
Why pin exact versions?
Pinning versions (flask==3.1.3) ensures your container builds the same way every time. Without pins, a future update could break your app without you changing any code.
- Save requirements.txt.
- Confirm both files exist in the Explorer sidebar on the left side of VS Code. You should see app.py and requirements.txt listed under docker-visit-counter.
Don't see both files in the sidebar?
- Make sure you saved each file inside the docker-visit-counter folder, not in a parent or subfolder.
- If the Explorer sidebar is hidden, toggle it with Ctrl+B.
- Check that the filenames are exactly app.py and requirements.txt with no extra spaces or uppercase letters.
Still stuck? Help me find my files in VS Code.
Write the Dockerfile
A Dockerfile is a recipe that tells Docker how to build your application into an image. Each line is an instruction that creates a layer in the final image.
- Create a new file with Ctrl+N and save it as Dockerfile (no file extension) in the docker-visit-counter folder.
- Paste the following into your Dockerfile:
# Start from a lightweight Python image
FROM python:3.12-slim
# Set the working directory inside the container
WORKDIR /app
# Copy requirements first and install (leverages layer caching)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy the application code
COPY app.py .
# Document which port the app listens on
EXPOSE 5000
# Run the Flask app when the container starts
CMD ["python", "app.py"]
What does each instruction do?
- FROM python:3.12-slim starts from an official Python image based on a minimal Debian system. The slim variant keeps the image small.
- WORKDIR /app creates and switches to the /app directory inside the container. All following commands run from here.
- COPY requirements.txt . then RUN pip install installs dependencies in a separate layer. Because this layer is cached, rebuilds are faster when only your app code changes.
- COPY app.py . adds your application code after dependencies are installed.
- CMD ["python", "app.py"] sets the default command that runs when a container starts from this image.
- Save Dockerfile.
- Confirm it appears in the Explorer sidebar alongside app.py and requirements.txt.
File saved as Dockerfile.txt instead?
Windows sometimes appends .txt to files without an extension. If your file shows as Dockerfile.txt in the sidebar, right-click it and select Rename to remove the .txt extension.
Need more help? My Dockerfile has a .txt extension and I can't remove it.
✔️ Awesome, I've got everything!
Great. Make sure you've saved your Dockerfile before moving on.
ⓧ I'd like to double check the full code
Here is the complete Dockerfile without comments:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]
Add a .dockerignore file
Just like .gitignore keeps files out of Git, a .dockerignore keeps unnecessary files out of your Docker build context. This makes the image smaller and prevents sensitive files from being copied in.
- Create a new file with Ctrl+N and save it as .dockerignore (note the dot at the start) in the docker-visit-counter folder.
- Paste the following into .dockerignore:
__pycache__
*.pyc
.git
.env
.venv
What is each entry excluding?
- __pycache__ and *.pyc are compiled Python bytecode files that get regenerated inside the container anyway.
- .git is your version control history. It adds bulk with no runtime value.
- .env and .venv are local environment files and virtual environments that should never ship inside a container.
- Save .dockerignore.
- Check the Explorer sidebar. You should now see four files: app.py, requirements.txt, Dockerfile, and .dockerignore.
Don't see .dockerignore in the sidebar?
Files starting with a dot are sometimes hidden by default. In VS Code, they should still appear in the Explorer sidebar. If you can't see it, make sure you saved it inside the docker-visit-counter folder and that the filename starts with a dot (.dockerignore, not dockerignore).
Still stuck? Help me find my .dockerignore file.
Your application code and Docker configuration are ready. Next, you'll build this into a real Docker image and try running it as a container.
Build and Run Your Custom Image
You have all the ingredients for a container: a Python app, its dependencies, and a Dockerfile with build instructions. Now it's time to package everything into a Docker image.
A Docker image is a portable snapshot of your entire application environment. Once built, it can run on any machine with Docker installed and produce identical results every time.
In this step, get ready to:
- Build a Docker image from your Dockerfile.
- Run the container and observe what happens without Redis.
- Understand why multi-container orchestration is needed.
Build your Docker image
Your Dockerfile describes how to assemble the image, but Docker needs an explicit command to actually build it. The build process reads each instruction, executes it, and saves the result as a layer.
- Build your Docker image by running this command in the VS Code terminal:
docker build -t visit-counter:1 .
What does this command do?
- -t visit-counter:1 tags the image with the name visit-counter and version 1.
- The . at the end tells Docker to use the current directory as the build context. Docker looks here for the Dockerfile and all referenced files.
- Each Dockerfile instruction (FROM, COPY, RUN) creates a cached layer. If you rebuild later without changing that layer, Docker skips it for a faster build.
- Wait for the build to finish. You should see output showing each step completing successfully.
- Confirm the image exists by running:
docker images
You should see visit-counter with tag 1 in the list.
Build failed or image not showing?
- Make sure your terminal is inside the docker-visit-counter folder. Run cd ~/Desktop/docker-visit-counter to get there.
- Check that your Dockerfile has no file extension. It should be named exactly Dockerfile, not Dockerfile.txt.
- Verify Docker Desktop is running. The build command communicates with the Docker daemon, which is started by Docker Desktop.
Help me fix my Docker build error
Run the container and observe the Redis failure
You have an image, but images are just blueprints. To actually start your app, you need to run the image as a container.
- Run your container by executing this command:
docker run --rm -p 5000:5000 --name visit-counter visit-counter:1
What do these flags mean?
- --rm automatically removes the container when it stops, keeping your system clean.
- -p 5000:5000 maps port 5000 on your machine to port 5000 inside the container. This is how you access the app from your browser.
- --name visit-counter gives the container a friendly name instead of a random string.
- visit-counter:1 is the image to run.
You should see Flask's startup message in the terminal, confirming the server is running on port 5000.
- Open your browser and go to http://localhost:5000.
Instead of seeing the visit counter, your browser shows an error page. The terminal displays a connection error because the app tried to reach a Redis database that does not exist yet.
Why does this fail?
Your app.py connects to a Redis host named redis (the default in the code). Inside a standalone container, there is no other container named redis listening on port 6379.
This is exactly the problem Docker Compose solves. It lets you define multiple containers that share a network, so they can find each other by service name.
Port already in use?
- If you see an error about port 5000 being in use, another process is already listening on that port. Stop the other process, or change the host port to -p 5001:5000 and visit http://localhost:5001 instead.
- If the container exits immediately without Flask starting, check that app.py is saved with the correct content from the previous step.
Help me fix the port conflict when running my Docker container
Stop the container
- Return to the terminal where the container is running.
- Press Ctrl+C to stop the container.
Because you used the --rm flag, the container is automatically removed after stopping.
What did you just prove?
Your image builds correctly and Flask starts inside the container. The only missing piece is a running Redis instance that the app can reach.
Running two separate docker run commands and manually wiring them together would be tedious and fragile. Docker Compose handles this for you in a single file.
Your image is ready and tested. Next up, you'll use Docker Compose to spin up both the Flask app and Redis together so the visit counter actually works.
Orchestrate with Docker Compose
In the last step, you confirmed your Docker image builds and starts correctly. The app failed to connect to Redis because no Redis container existed.
Docker Compose solves this problem. It lets you define multiple containers in a single file and launch them together as a coordinated stack. In this step, you'll create a compose.yaml file that runs your Flask app alongside a Redis database.
In this step, get ready to:
- Create a compose.yaml file defining two services.
- Start both containers with a single command.
- Explore the running stack with Compose management commands.
Create a Docker Compose file
Docker Compose reads a YAML file that describes your entire application stack. Each container becomes a "service" in the file. Compose automatically creates a shared network so your services can reach each other by name.
- In VS Code, create a new file called compose.yaml in the root of your docker-visit-counter folder.
- Add the following configuration to define your two services:
services:
# Flask web app - built from our Dockerfile
web:
build: .
ports:
- "5000:5000"
environment:
- REDIS_HOST=redis
depends_on:
- redis
# Redis database - uses the official image
redis:
image: redis:7
What does this file do?
This file defines two services that Docker Compose will run together:
- web builds an image from your Dockerfile, maps port 5000 on your machine to port 5000 in the container, and passes REDIS_HOST=redis as an environment variable so your app knows where to find the database.
- redis pulls the official redis:7 image from Docker Hub. No build needed.
- depends_on tells Compose to start Redis before the web app.
Why can the app reach Redis by the name "redis"?
Docker Compose automatically creates a private network for your stack. Each service is reachable by its service name as a hostname. Your Flask app connects to redis (the service name), and Compose resolves that to the Redis container's IP address.
- Save compose.yaml.
YAML syntax error?
YAML is whitespace-sensitive. Make sure you use spaces (not tabs) for indentation, and that each level is indented by exactly two spaces. Check that the ports and environment entries each have a dash followed by a space before the value.
help me fix a YAML indentation error in my compose.yaml
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
services:
web:
build: .
ports:
- "5000:5000"
environment:
- REDIS_HOST=redis
depends_on:
- redis
redis:
image: redis:7
Start the stack and verify the app works
With your Compose file saved, a single command will build the web image and start both containers together.
- Start the entire stack by running this command in your terminal:
docker compose up --build
What does this command do?
docker compose up starts all services defined in compose.yaml. The --build flag forces a fresh image build for any service that uses build: instead of a pre-built image. You'll see logs from both the web and redis containers interleaved in your terminal.
- Open http://localhost:5000 in your browser.
- Refresh the page several times.
Each refresh increments the visit counter. The number goes up because your Flask app is successfully storing the count in Redis.
Seeing a connection error in the browser?
If localhost:5000 refuses to connect, check your terminal for error messages. Common causes:
- Another process is already using port 5000. Stop it or change the port mapping in compose.yaml to "5001:5000".
- The build failed. Scroll up in the terminal output to find the error. A typo in requirements.txt or Dockerfile is the most common cause.
- Docker Desktop is not running. Open Docker Desktop and wait for it to fully start before retrying.
help me troubleshoot why my Docker Compose stack won't connect on localhost:5000
Explore the running stack
Docker Compose gives you commands to inspect and interact with your running services without stopping them. You'll run these in a separate terminal so the stack keeps running.
- Open a new terminal tab in VS Code by pressing Ctrl+Shift+`.
- View both running services by running this command:
docker compose ps
You should see both the web and redis services listed with a status of "running" and port mappings.
- View the logs from your Flask app by running:
docker compose logs web
You should see HTTP request logs showing each time you refreshed the page in your browser.
- Query the Redis database directly to see the stored visit count by running:
docker compose exec redis redis-cli GET visits
What does this command do?
docker compose exec runs a command inside a running container. Here you're running redis-cli GET visits inside the redis container. This reads the visits key directly from the Redis database. The number returned should match what you see in the browser.
The number printed matches the count displayed in your browser. This confirms your Flask app and Redis are communicating correctly through the Compose network.
Getting "no such service" error?
Make sure your second terminal is in the docker-visit-counter folder. Docker Compose looks for compose.yaml in the current directory. Run cd ~/Desktop/docker-visit-counter to navigate there.
help me fix a Docker Compose error about services not being found
Your multi-container app is running and proven. Next up, you'll push your image to Docker Hub so anyone in the world can pull and run it.
Push Your Image to Docker Hub
Your multi-container app is running locally with Flask and Redis communicating seamlessly.
The final step in the container workflow is publishing your image so anyone can download and run it. Docker Hub is the default public registry for Docker images. By pushing your image there, a teammate or deployment server can pull it with a single command.
In this step, get ready to:
- Stop the running stack and tag your image for Docker Hub.
- Push your image to Docker Hub.
- Verify your published image by pulling it fresh.
Stop the stack and tag your image
Before you push, you need to stop the running containers and give your image a name that includes your Docker Hub username. This tells Docker which registry and account to upload to.
- Stop the running stack by running this command:
docker compose down
What does this command do?
`docker compose down` stops all running containers and removes the containers and networks that `docker compose up` created. Your source files and built images remain untouched.
- You should see output confirming both the web and redis containers have been stopped and removed.
- Tag your local image for Docker Hub by running this command:
docker tag visit-counter:1 [[DOCKERHUB_USERNAME="your Docker Hub username"]]/visit-counter:1
What does tagging do?
The docker tag command creates a new reference pointing to the same image. The format username/repository:tag tells Docker the image belongs to your Docker Hub account.
No data is copied. Both visit-counter:1 and yourusername/visit-counter:1 point to the same underlying image layers.
- Confirm the new tag exists by running:
docker images
- You should see both visit-counter and your yourusername/visit-counter tag listed with the same image ID.
Don't see your tagged image?
- Make sure you typed your Docker Hub username correctly. It must match the username you used when running docker login earlier.
- Check that you included the :1 tag at the end of both the source and target image names.
Help me fix my docker tag command.
Push your image to Docker Hub
Now that your image has a tag referencing your Docker Hub account, you can upload it. Docker pushes each layer individually, so only new or changed layers need uploading.
- Push the tagged image by running this command:
docker push [[DOCKERHUB_USERNAME="your Docker Hub username"]]/visit-counter:1
Docker uploads each layer to Docker Hub. You will see progress bars for each layer as it uploads.
What does docker push do?
The docker push command uploads your image layers to the registry specified in the tag. Since your tag starts with your Docker Hub username (and no other host is specified), Docker defaults to docker.io as the registry.
Layers that already exist on the registry (like the python:3.12-slim base layers) are skipped. Only your application layers are uploaded.
- Once the push completes, open your browser and go to https://hub.docker.com.
- Sign in and look for your visit-counter repository. You should see it listed with the 1 tag.
Push failed with access denied?
- Make sure you are still logged in. Run docker login again and enter your Docker Hub credentials.
- Check that the username in your tag matches your Docker Hub username exactly (case-sensitive).
Help me fix my docker push.
Verify your published image
The real test of a published image is whether someone else can pull and run it. You can simulate this by removing your local copy and pulling it fresh from Docker Hub.
- Remove the local copy of your tagged image by running this command:
docker rmi [[DOCKERHUB_USERNAME="your Docker Hub username"]]/visit-counter:1
What does docker rmi do?
The docker rmi command removes an image reference from your local machine. Since visit-counter:1 (the original tag) still points to the same layers, Docker only removes the tag reference. The layers stay cached until no tag references them.
- Pull the image fresh from Docker Hub by running this command:
docker pull [[DOCKERHUB_USERNAME="your Docker Hub username"]]/visit-counter:1
- You should see Docker downloading the image layers from Docker Hub. Once complete, the status shows the image digest and that it has been pulled successfully.
- Confirm the image is available locally by running:
docker images
- You should see your yourusername/visit-counter image listed again, downloaded from Docker Hub.
Pull failed or image not found?
- Double-check that the push completed successfully in the previous substep. Visit your Docker Hub profile to confirm the repository exists.
- Verify the tag matches exactly. docker pull username/visit-counter:1 is case-sensitive and must match what you pushed.
Help me pull my image from Docker Hub.
Secret mission
Add a Stats Endpoint
Your visit counter works, but what if you want to know how long the container has been running or check total traffic at a glance? In this secret mission, you'll add a /stats endpoint that returns live metrics as JSON.
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 resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Running Docker Compose stack (web and redis containers).
- Local Docker images (visit-counter:1, visit-counter:2, and tagged copies for Docker Hub).
- Docker Hub repository (yourusername/visit-counter).
- Project folder docker-visit-counter on your Desktop.
Keep everything running
No action needed. Choose this if you're still actively experimenting with Docker containers or plan to keep building on this project.
- Your Docker Compose stack stays running until you restart your computer or manually stop it.
- Docker Desktop continues using system resources (RAM and CPU) while running.
- Your Docker Hub account and repository remain active at no cost for personal use.
Pause - I'll come back to this later
Shut down running containers to free up memory and CPU. Your files, images, and Docker Hub repository remain intact for when you return.
- Stop the running stack by opening PowerShell in your docker-visit-counter folder and running:
docker compose down
- Optionally quit Docker Desktop from the system tray to free up additional system resources.
- When you're ready to resume, open Docker Desktop and run docker compose up --build from the project folder.
Delete - I don't want to use this again
Remove all project containers, images, and files. This is a full teardown of everything you created during this project.
- Stop the running stack and remove its containers and network by running:
docker compose down
- Remove all local Docker images created during the project by running:
docker rmi visit-counter:1 visit-counter:2 [[DOCKER_USERNAME="your Docker Hub username"]]/visit-counter:1 [[DOCKER_USERNAME="your Docker Hub username"]]/visit-counter:2
- Remove the base images that Docker pulled during the project by running:
docker rmi python:3.12-slim redis:7 hello-world
- Delete your Docker Hub repository:
- Go to https://hub.docker.com and sign in.
- Select My Hub, then Repositories.
- Select your visit-counter repository.
- Select the Settings tab.
- Select Delete repository.
- Enter the repository name to confirm, then select Delete Repository Forever.
- Delete the project folder from your Desktop by running:
cd ~\Desktop
Remove-Item -Recurse -Force docker-visit-counter
That's a Wrap!
That's a Wrap!
Nice work! You just containerized a Python web application, orchestrated it with a Redis database, and shipped the image to Docker Hub.
You've learned how to:
- Write a Dockerfile to containerize a Python Flask web app into a portable, reproducible image.
- Use Docker Compose to orchestrate a multi-container application with a web server and Redis database communicating over a shared network.
- Push a custom image to Docker Hub so anyone can pull and run your application with a single command.
- Added a /stats endpoint that returns uptime, total visits, and server time as JSON.
Ready to quiz yourself?