Containerize a Python Flask App

Package a Python Flask health-check API into a portable Docker container.

Introduction

30 Second Summary

Every app you use on your phone went through a packaging process before it reached you. In the software world, that packaging step is called containerization, and it's the single most in-demand skill for DevOps roles.

In this project, you will build a Python Flask health-check API and package it inside a Docker container. You'll write a Dockerfile from scratch, build an image, and run your app in a portable container that works the same on any machine.

What You'll Build

A containerized Python Flask health-check API that responds to HTTP requests with system status information, running inside a Docker container on your local machine.

By the end of this project, you'll have:

  • A working Flask API with multiple endpoints running inside a Docker container.
  • A Dockerfile you wrote yourself that packages the app into a portable image.
  • Hands-on experience with the core Docker workflow (write, build, run) that appears in every DevOps job posting.
  • Secret Mission: Push your image to Docker Hub so anyone in the world can pull and run your app with a single command.

Are there any prerequisites?

None. This guide includes setup for all required tools, including Docker Desktop and WSL 2. You just need a Windows computer with VS Code installed.

Install Docker Desktop and Verify Your Setup

Docker runs containers inside a lightweight Linux environment. On Windows, that environment is provided by WSL 2 (Windows Subsystem for Linux).

In this step, you'll enable WSL 2, install Docker Desktop, and confirm everything is working by running your first container.

In this step, get ready to:
  • Enable WSL 2 on Windows.
  • Install Docker Desktop.
  • Verify Docker is working with a test container.
Enable WSL 2 on Windows

Docker Desktop uses WSL 2 as its backend to run Linux containers on Windows. You need to enable WSL 2 before installing Docker.

  • Press the Windows key to open your search bar.
  • Type PowerShell and look for Windows PowerShell in the results.
  • Right-click Windows PowerShell and select Run as administrator.
  • Click Yes if a User Account Control prompt appears.
  • Install WSL 2 by running this command:
wsl --install

What does this command do?

This installs the WSL 2 feature along with a default Linux distribution (Ubuntu). WSL 2 gives Docker a real Linux kernel to run containers on your Windows machine.

You may be asked to restart your computer

If the terminal says a restart is required, save your work, restart your machine, and then reopen PowerShell as administrator to continue.

  • After the install completes (and after any restart), verify WSL 2 is working by running:
wsl --version

You should see version details including the WSL version number and Linux kernel version.

✔️ I see version details

WSL 2 is ready. You can move on to installing Docker Desktop.

ⓧ I see an error or no version info

If version details do not appear, you may be running an older inbox version of WSL that needs updating.

  • Try updating WSL by running:
wsl --update
  • Restart your computer if prompted.
  • Run wsl --version again to confirm it works.
Install Docker Desktop

Docker Desktop is the application that manages containers on your machine. It provides the Docker engine, a graphical dashboard, and the command-line tools you need.

  • Open your browser and go to the Docker Desktop install page: https://docs.docker.com/desktop/setup/install/windows-install/
  • Click the Docker Desktop for Windows - x86_64 download button near the top of the page.
  • Once the download finishes, double-click Docker Desktop Installer.exe to run the installer.
  • When the installer asks you to choose an installation mode, select the per-user option (recommended). This does not require administrator privileges.
  • Ensure the Use WSL 2 instead of Hyper-V option is selected on the Configuration page.
  • Follow the remaining prompts and click Close when the installation completes.

Why per-user installation?

The per-user installation mode does not require administrator privileges to install or update. It uses the WSL 2 backend only, which is exactly what you need for this project.

  • Start Docker Desktop by pressing the Windows key, typing Docker, and selecting Docker Desktop from the search results.
  • Accept the Docker Subscription Service Agreement when it appears.

Is Docker Desktop free?

Docker Desktop is free for personal use, education, and small businesses (fewer than 250 employees and less than $10 million in annual revenue). You will not be charged for this project.

Wait for Docker Desktop to finish starting up. You will see the Docker dashboard once it is ready.

Docker Desktop stuck on starting?

Docker Desktop can take 1-2 minutes to start for the first time. If it stays stuck, try restarting your computer to ensure WSL 2 is fully initialized, then open Docker Desktop again.

Help me troubleshoot Docker Desktop not starting

Confirm Docker is working

With Docker Desktop running, you can now use Docker commands in your terminal. Let's verify the installation works end to end.

  • Press the Windows key to open your search bar.
  • Type Windows Terminal and press Enter to open it.

No Windows Terminal?

If Windows Terminal is not installed, search for PowerShell instead. Both work the same for Docker commands.

  • Check your Docker version by running:
docker --version

You should see a version string like Docker version 27.x.x (your exact number may differ).

✔️ I see a version number

Docker is installed and your terminal can communicate with the Docker engine. One more check to go.

ⓧ Command not found or error

  • Make sure Docker Desktop is running. Look for the Docker whale icon in your system tray (bottom-right corner of your screen).
  • If Docker Desktop is running but the command still fails, close your terminal and open a new one. The terminal needs to refresh its path after Docker installs.
  • Try running the command again in the new terminal.
  • Run your first container to confirm the full Docker workflow is operational:
docker run hello-world

What does this command do?

This command tells Docker to pull the hello-world image from Docker Hub (a public container registry) and run it as a container. The container prints a success message and exits.

This tests the entire chain: your terminal talks to the Docker engine, the engine pulls an image from the internet, and it runs a container successfully.

You should see Hello from Docker! followed by a message confirming your installation is working correctly.

✔️ I see Hello from Docker!

Your Docker setup is fully operational. You have WSL 2 running, Docker Desktop installed, and the full container workflow working end to end.

ⓧ I see an error

  • If you see a network error, check that your internet connection is active. Docker needs to download the image from Docker Hub.
  • If you see a permissions error, make sure Docker Desktop is running (check for the whale icon in your system tray).
  • If the error mentions the Docker daemon, restart Docker Desktop and wait for it to fully start before trying again.

Help me fix my docker run hello-world error

Your environment is locked and loaded. Next up, you'll create the Python Flask application that will run inside your container.

Create the Flask Application

Docker installed and verified. Now you need something to containerize. Before writing any Docker instructions, you need a working application that Docker can package into an image.

In this step, you'll build a Python Flask health-check API. This is the exact kind of microservice that DevOps teams containerize, deploy, and monitor in production.

In this step, get ready to:
  • Create a project folder and open it in VS Code.
  • Pin your Python dependency in requirements.txt.
  • Write a Flask API with health-check endpoints in app.py.
Create the project folder and pin Flask

Every project starts with a dedicated folder. You'll create one on your Desktop so it's easy to find, then open it in VS Code where you'll write all your project files.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir health-api
cd health-api
  • Open this folder in VS Code by running:
code .
  • Confirm the folder exists by running:
ls ~/Desktop

You should see health-api in the output. VS Code opens with an empty folder ready for your files.

VS Code didn't open?

Make sure VS Code is installed and that the code command is available in your terminal. In VS Code, press Ctrl+Shift+P, type shell command, and select Install 'code' command in PATH.

help me get the code command working in my terminal

Python projects use a requirements.txt file to list their dependencies. This tells Docker exactly which packages to install inside the container.

  • In VS Code, hover over the Explorer sidebar (left panel) and click the New File icon.
  • Type requirements.txt as the filename and press Enter.
  • Paste the following content into the file:
flask==3.1.3

What does this file do?

This pins Flask to version 3.1.3. Pinning a specific version means your container will always install the same version of Flask, no matter when or where you build it. Reproducibility is a core principle of containers.

  • Save the file with Ctrl+S.

You should see requirements.txt listed in the Explorer sidebar with no unsaved-changes dot on its tab.

Write the Flask health-check API

Your API will have three endpoints that return JSON responses. Health-check endpoints like these are used in production for monitoring. Load balancers and orchestrators ping them to know whether a service is alive.

  • Create a new file in VS Code by clicking the New File icon in the Explorer sidebar.
  • Name it app.py and press Enter.
  • Start by adding the imports and creating the Flask app instance:
from flask import Flask, jsonify
from datetime import datetime, timezone
import os

app = Flask(__name__)

What does this code do?

Flask is the web framework that handles incoming HTTP requests. jsonify converts Python dictionaries into JSON responses.

datetime and os are standard Python libraries. You'll use them to include timestamps and system info in your API responses. app = Flask(__name__) creates the Flask application instance that your routes attach to.

  • Below the app instance, add the home endpoint:

@app.route("/")
def home():
    return jsonify({
        "message": "Welcome to the Health Check API",
        "endpoints": ["/health", "/info", "/version"]
    })

What does this endpoint do?

The @app.route("/") decorator tells Flask to call home() when someone visits the root URL. It returns a welcome message and a list of available endpoints so users know what the API offers.

  • Below the home function, add the health-check endpoint:

@app.route("/health")
def health():
    return jsonify({
        "status": "healthy",
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "version": "1.0"
    })

What does this endpoint do?

The /health endpoint returns the current status of the service, a UTC timestamp, and the app version. In production, orchestrators like Kubernetes ping this endpoint every few seconds to decide whether to restart a failing container.

  • Below the health function, add the info endpoint and the startup block:

@app.route("/info")
def info():
    return jsonify({
        "hostname": os.environ.get("HOSTNAME", "unknown"),
        "platform": "Docker",
        "python_version": os.sys.version
    })


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

What does this code do?

The /info endpoint exposes container environment details. HOSTNAME inside a Docker container is automatically set to the container's short ID, so you'll be able to confirm your code is running inside Docker.

The if __name__ == "__main__" block starts the Flask development server on all network interfaces (0.0.0.0) and port 5000. Binding to 0.0.0.0 is required so that traffic from outside the container can reach the app.

  • Save app.py with Ctrl+S.

You should see both requirements.txt and app.py in the VS Code Explorer sidebar with no unsaved-changes indicators.

Seeing a red squiggle or import error in VS Code?

VS Code may highlight from flask import Flask as an error because Flask is not installed on your local machine. This is expected. The package will be installed inside the Docker container, not on your system.

help me understand why VS Code shows import errors for Flask

Review your app structure

Before moving on to the Dockerfile, confirm that both files are complete and match what you expect.

✔️ Awesome, I've got everything!

Great. Double check you've saved both files and that there are no typos in your route decorators or function names.

ⓧ I'd like to double check the full code

Here is the complete requirements.txt:

flask==3.1.3

Here is the complete app.py:

from flask import Flask, jsonify
from datetime import datetime, timezone
import os

app = Flask(__name__)


@app.route("/")
def home():
    return jsonify({
        "message": "Welcome to the Health Check API",
        "endpoints": ["/health", "/info", "/version"]
    })


@app.route("/health")
def health():
    return jsonify({
        "status": "healthy",
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "version": "1.0"
    })


@app.route("/info")
def info():
    return jsonify({
        "hostname": os.environ.get("HOSTNAME", "unknown"),
        "platform": "Docker",
        "python_version": os.sys.version
    })


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

Your application is ready. Next up, you'll write a Dockerfile that teaches Docker how to package this app into a portable container image.

Write the Dockerfile

Your Flask application has three endpoints ready to serve requests. Now it's time to package this app into a container image that can run identically on any machine.

A Dockerfile is the recipe that tells Docker exactly how to build your image. Each instruction creates a layer, and the order of those instructions matters for build performance.

In this step, get ready to:
  • Write a Dockerfile that packages your Flask app into a container image.
  • Create a .dockerignore file to keep your build context clean and fast.
Create the Dockerfile and set up the base image

The Dockerfile tells Docker which operating system to start from, what files to include, and how to run your app.

  • In VS Code, right-click in the Explorer sidebar.
  • Click New File.
  • Type Dockerfile (capital D, no file extension) and press Enter.

No file extension

The file must be named exactly Dockerfile with no extension. Some editors automatically add .txt which causes the build to fail.

If you see Dockerfile.txt in the Explorer sidebar, rename it by right-clicking and selecting Rename.

  • Paste the following code into your new Dockerfile:
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .

What does this code do?

  • FROM python:3.12-slim starts from a lightweight Linux image with Python 3.12 pre-installed. The slim variant is Debian-based and avoids compatibility issues that Alpine can cause with Python packages.
  • WORKDIR /app creates a /app directory inside the image and sets it as the working directory for all following instructions.
  • COPY requirements.txt . copies only the dependency file into the image first. This is intentional for build caching.

Why copy requirements.txt separately?

Docker caches each layer. If you copy dependency manifests first, install dependencies, then copy the rest of the source, Docker can skip re-running the install step when only your app code changes.

This saves significant time during iterative development. You will see this caching in action in the next step.

  • Save Dockerfile.
  • Confirm you see Dockerfile (no extension) listed in the VS Code Explorer sidebar alongside app.py and requirements.txt.

Don't see Dockerfile in the sidebar?

  • If you see Dockerfile.txt, your editor added an extension. Right-click the file, select Rename, and remove the .txt part.
  • If the file isn't visible at all, make sure you created it inside the project folder (the same folder that contains app.py), not in a subfolder.

Help me fix my Dockerfile naming issue.

Add the install and startup instructions

The first section sets up the base environment. Now you need to install your dependencies, bring in your app code, and tell Docker how to start the application.

  • In Dockerfile, add the following code below what you just pasted:
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]

What does this code do?

  • RUN pip install --no-cache-dir -r requirements.txt installs Flask inside the image. The --no-cache-dir flag keeps the image smaller by skipping pip's download cache.
  • COPY . . copies the rest of your project files (including app.py) into the image's working directory.
  • EXPOSE 5000 documents that the container listens on port 5000. This is informational and helps other developers understand your container's interface.
  • CMD ["python", "app.py"] sets the default command that runs when the container starts. This launches your Flask server.
  • Save Dockerfile.

You won't see a visible change yet. The next step uses this Dockerfile to build your container image.

Seeing a red underline or syntax warning?

  • VS Code may show warnings if it does not have the Docker extension installed. These warnings do not affect the build. You can safely ignore them.
  • Make sure each instruction (FROM, WORKDIR, COPY, RUN, EXPOSE, CMD) starts on its own line with no leading spaces.

Help me check my Dockerfile for syntax errors.

✔️ Awesome, I've got everything!

Great. Double check you've saved your Dockerfile before moving on.

ⓧ I'd like to double check the full code

Here is the complete Dockerfile. Make sure yours matches exactly:

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]
Create a .dockerignore file

When Docker builds your image, it sends everything in the project folder to the Docker engine as the "build context." A .dockerignore file tells Docker which files to skip. This keeps builds fast and avoids accidentally baking sensitive values into an image layer.

  • In VS Code, right-click in the Explorer sidebar and click New File.
  • Name the file .dockerignore (starting with a dot) and press Enter.
  • Paste the following content into the file:
__pycache__
*.pyc
*.pyo
.git
.gitignore
.env
*.md

What does this file do?

  • __pycache__, *.pyc, and *.pyo are Python cache files that get regenerated inside the container. Including them wastes space.
  • .git is your version control history. It can be large and is irrelevant inside a container.
  • .env often contains secrets like API keys. Excluding it prevents credentials from being baked into the image.
  • Save .dockerignore.
  • Confirm you see .dockerignore in the Explorer sidebar alongside Dockerfile, app.py, and requirements.txt.

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. If you created the file outside VS Code, check that your file manager is set to show hidden files.
  • Make sure the filename starts with a dot. It should be .dockerignore, not dockerignore.

Help me find my .dockerignore file.

✔️ Awesome, I've got everything!

Great. Double check you've saved your .dockerignore before moving on.

ⓧ I'd like to double check the full code

Here is the complete .dockerignore file:

__pycache__
*.pyc
*.pyo
.git
.gitignore
.env
*.md

Your Dockerfile and .dockerignore are ready. Next up, you will build the image and run your first container.

Build and Run Your Container

You have all the ingredients in place. Your Dockerfile tells Docker exactly how to package your Flask app, and your .dockerignore keeps the build clean.

Now it's time to turn those files into a running container. You'll build an image, launch it, and confirm your API responds to real HTTP requests from your browser.

In this step, get ready to:
  • Build a Docker image from your Dockerfile.
  • Run the image as a container with port mapping.
  • Verify the API is responding in your browser and inspect container logs.
Build the Docker image

A Docker image is a snapshot of your application and its environment, frozen at a point in time. When you build an image, Docker reads your Dockerfile line by line and creates a layer for each instruction.

  • Build your Docker image by running this command in the terminal:
docker build -t health-api .

What does this command do?

  • docker build tells Docker to read your Dockerfile and construct an image from it.
  • -t health-api tags (names) the image health-api so you can refer to it easily later.
  • . (the dot) sets the build context to the current directory. Docker sends all non-ignored files in this folder to the build engine.

You should see Docker process each layer step by step. It pulls the python:3.12-slim base image, installs your dependencies, and copies in your app code.

When it finishes, you should see a message confirming the image was tagged as health-api.

What are layers?

Each instruction in your Dockerfile (FROM, COPY, RUN) creates a layer. Docker caches these layers so that if you rebuild and only your app code changed, it reuses the cached dependency layer instead of re-downloading everything.

This is why the Dockerfile copies requirements.txt and installs packages before copying the rest of the code. Dependencies change less often, so that layer stays cached longer.

Seeing an error during the build?

  • Make sure Docker Desktop is running. You can check for its icon in your system tray.
  • Confirm you are in the project folder that contains your Dockerfile. Run ls and check that Dockerfile appears in the output.
  • If you see a file-not-found error mentioning Dockerfile, your editor may have saved it as Dockerfile.txt. Rename it to remove the extension.

Still stuck? Help me troubleshoot my Docker build error.

Run the container

An image is a blueprint. A container is a live, running instance of that blueprint. Now you'll launch your image as a container and map its internal port to your machine so you can access the API.

  • Start your container by running this command:
docker run -d -p 5000:5000 --name my-health-api health-api

What do these flags mean?

  • -d runs the container in detached mode (in the background), freeing up your terminal.
  • -p 5000:5000 maps port 5000 on your machine to port 5000 inside the container. Without this, the container's network is isolated and you cannot reach the app.
  • --name my-health-api gives the container a friendly name instead of a random one.
  • health-api at the end is the image to run (the one you just built).

Docker prints a long container ID hash. This confirms the container launched successfully.

  • Confirm the container is running by running:
docker ps

You should see a table with one row showing my-health-api with a status of Up and port 0.0.0.0:5000->5000/tcp in the PORTS column.

Container not showing in docker ps?

  • If docker ps shows an empty table, the container may have exited immediately. Run docker ps -a to see stopped containers, then docker logs my-health-api to see what went wrong.
  • If you see a port conflict error, another process is already using port 5000. Stop it or change the host port (e.g. -p 5001:5000).

Need more help? Help me debug why my Docker container won't stay running.

Test the API in your browser and check logs

Your container is running and its port is mapped to your machine. That means you can hit the Flask endpoints from your browser just like you would with any website.

  • Open your browser and navigate to http://localhost:5000.

You should see a JSON response with a welcome message and a list of available endpoints.

  • Now navigate to http://localhost:5000/health.

You should see a JSON response with "status": "healthy", a timestamp, and a version number. This is the same pattern used in production health checks for load balancers and orchestration tools.

Why health endpoints matter

In production, tools like Kubernetes and AWS load balancers ping a /health endpoint every few seconds. If it stops responding, the system automatically restarts or replaces the container.

  • Check your container's logs by running this command:
docker logs my-health-api

You should see Flask's startup message and the HTTP requests your browser just made. Each line shows the endpoint, HTTP method, and response code.

What are container logs?

Everything your app prints to the console (stdout/stderr) is captured by Docker. The docker logs command lets you read that output without needing to attach to the running container. This is how DevOps teams debug issues in production.

Browser showing "connection refused"?

  • Double-check that docker ps still shows your container running. If it crashed, the logs will explain why.
  • Make sure you are using http:// not https://. Your Flask app does not have SSL configured.
  • If you changed the host port earlier (e.g. -p 5001:5000), use that port number in the URL instead.

Still not working? Help me figure out why I cannot reach my containerized Flask app in the browser.

Your API is live inside a Docker container. Next up, you'll make a code change and rebuild to experience the Docker development loop firsthand.

Iterate and Rebuild

You have a working containerized API. In real DevOps work, code changes happen constantly. You need to rebuild containers every time you update the application.

Docker makes this fast with a build cache. When you rebuild an image, Docker reuses layers that have not changed. Only the affected layers get rebuilt, saving significant time on every iteration.

In this step, get ready to:
  • Add a new API endpoint and rebuild the Docker image.
  • Replace the running container with the updated version.
  • Inspect the image layers to understand how Docker structures builds.
Add a new endpoint and rebuild the image

Your app currently has three endpoints. You will add a fourth one that returns version information. This is a common pattern for tracking which version of your code is deployed.

  • Switch back to VS Code and open app.py.
  • Find the info() function. Scroll down to the blank line just above if __name__ == "__main__": at the bottom of the file.
  • Add this new endpoint in that space:
@app.route("/version")
def version():
    return jsonify({"version": "2.0"})

What does this code do?

  • @app.route("/version") registers a new URL path that responds to HTTP requests at /version.
  • The function returns a simple JSON object with a version number. In production, teams update this value with each release so monitoring tools can confirm which version is live.
  • Save app.py.
  • You won't see a change yet. The running container still uses the old image. You need to rebuild and relaunch it.

Why doesn't my running container update automatically?

A Docker container runs from a snapshot of your code taken at build time. Editing your local files does not affect the container. You must rebuild the image and start a new container to see changes.

  • Rebuild the Docker image by running this command in your terminal:
docker build -t health-api .
  • Watch the build output. You should see CACHED next to several early steps (like COPY requirements.txt and RUN pip install). Only the COPY . . step and later layers rebuild because your app code changed.

Why are some layers cached?

Remember how you structured the Dockerfile to copy requirements.txt and install dependencies before copying the app code? That was intentional. Since requirements.txt did not change, Docker reuses the cached pip install layer. Only the layers after COPY . . get rebuilt.

This is why Dockerfile instruction order matters. Putting frequently-changing files later in the Dockerfile maximizes cache hits and keeps rebuilds fast.

Build not using the cache?

  • If every layer rebuilds from scratch, make sure you did not edit requirements.txt or the Dockerfile itself. Any change to an early layer invalidates everything after it.
  • Confirm you are running the build command from inside your project folder (the one containing the Dockerfile).

Why is Docker rebuilding all my layers even though I only changed app.py?

✔️ Awesome, I've got everything!

Great. Make sure you have saved app.py with the new version() endpoint added above if __name__ == "__main__":.

ⓧ I'd like to double check the full code

from flask import Flask, jsonify
from datetime import datetime, timezone
import os

app = Flask(__name__)


@app.route("/")
def home():
    return jsonify({
        "message": "Welcome to the Health Check API",
        "endpoints": ["/health", "/info", "/version"]
    })


@app.route("/health")
def health():
    return jsonify({
        "status": "healthy",
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "version": "1.0"
    })


@app.route("/info")
def info():
    return jsonify({
        "hostname": os.environ.get("HOSTNAME", "unknown"),
        "platform": "Docker",
        "python_version": os.sys.version
    })


@app.route("/version")
def version():
    return jsonify({"version": "2.0"})


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)
Stop the old container and run the updated image

Your old container is still running the previous version of the code. You need to stop it, remove it, and start a fresh one from the new image.

  • Stop and remove the old container by running these commands:
docker stop my-health-api
docker rm my-health-api

What do these commands do?

  • docker stop gracefully shuts down the running container by sending it a stop signal.
  • docker rm removes the stopped container entirely so you can reuse the name my-health-api for the new one.
  • Start a new container from the updated image by running this command:
docker run -d -p 5000:5000 --name my-health-api health-api
  • Open your browser and navigate to http://localhost:5000/version.
  • You should see a JSON response: {"version": "2.0"}.

Not seeing the /version endpoint?

  • Make sure you stopped AND removed the old container before starting the new one. If the old container is still running, the port is occupied and the new container will fail silently.
  • Run docker ps to confirm your new container is running. If it is not listed, run docker ps -a to check if it exited with an error.
  • Verify that the version() function in app.py is placed before if __name__ == "__main__": and that the file was saved before rebuilding.

Help me debug why my /version endpoint is not responding.

Inspect your image layers

Now that you have built an image twice, it is a good time to understand what is inside it. Docker provides commands to inspect image size and layer structure.

  • View all your local Docker images by running this command:
docker images

What should I see?

You will see a table listing your images. Look for the health-api row. The SIZE column shows how large the image is on disk. The python:3.12-slim base alone is around 120MB. Your app adds only a few MB on top.

  • Inspect the layer-by-layer breakdown of your image by running this command:
docker history health-api

What does this output show?

Each row represents a layer in your image. You can see the exact Dockerfile instruction that created each layer and how much space it added.

The largest layers come from the base image and the pip install step. Your COPY . . layer is tiny because it only contains your Python files. This is why the Docker build cache is so effective. Only the small layers at the end need rebuilding when you change your code.

Seeing truncated output?

  • If the CREATED BY column is cut off, try running docker history --no-trunc health-api to see the full commands for each layer.

Help me understand the output of docker history for my image.

You have now experienced the full Docker development loop: edit code, rebuild the image, replace the container. The build cache makes this cycle fast by only redoing the work that actually changed. This is the exact workflow that DevOps teams use to iterate on containerized services every day.

Secret mission

Push to Docker Hub

Your image works on your machine. But what about everyone else's? In this secret mission, you'll push your container image to Docker Hub, a public registry where anyone in the world can pull and run your app with a single command.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally using Docker Desktop, which is free for personal use. There are no ongoing cloud costs to worry about.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Docker container my-health-api (running on port 5000).
  • Docker image health-api (built locally).
  • Docker image your-username/health-api:v1 (pulled from Docker Hub).
  • Docker Hub repository your-username/health-api (remote, publicly available).
  • Project folder health-api on your Desktop.
  • Docker Desktop application (installed on your machine).

Keep everything running

No action needed. Choose this if you are still actively building or plan to continue experimenting with Docker soon.

  • Your container my-health-api continues running on port 5000.
  • Docker Desktop stays installed and ready for your next project.
  • Your Docker Hub repository remains publicly available for anyone to pull.

Pause - I'll come back to this later

Stop the running container and quit Docker Desktop to free up memory. Your files and images stay on disk for when you return.

  • Stop the running container by running this command:
docker stop my-health-api
  • Right-click the Docker Desktop icon in your system tray and select Quit Docker Desktop.
  • Your images, container, and project files remain on disk. When you are ready to continue, start Docker Desktop and run docker start my-health-api to resume.

Delete - I don't want to use this again

Remove all project resources and start fresh. Follow each step below to clean up everything.

  • Stop and remove the running container:
docker stop my-health-api
docker rm my-health-api
  • Remove all local Docker images created during this project:
docker rmi health-api
docker rmi your-username/health-api:v1
  • Delete your Docker Hub repository. Sign in to Docker Hub in your browser.
  • Select My Hub then Repositories.
  • Select your health-api 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:
cd ~\Desktop
Remove-Item -Recurse -Force health-api
  • Uninstall Docker Desktop. From the Windows Start menu, open Settings.
  • Select Apps, then Apps & features.
  • Select Docker Desktop from the list and choose Uninstall.
  • Select Uninstall again to confirm.

Nice Work!

Nice Work!

You just containerized a Python web application from scratch. You now have hands-on experience with the core Docker workflow that appears in every DevOps job posting.

Here's what you accomplished:

  • Built a Python Flask health-check API and wrote a Dockerfile that packages it into a portable container image.
  • Ran the container locally with port mapping and accessed your API endpoints from the browser.
  • Used the Docker build cache to iterate on your app and inspected image layers to understand how containers are structured.
  • Secret Mission: Pushed your image to Docker Hub, making it available to anyone in the world with a single docker pull command.

Ready to quiz yourself?