Deploy a Python API to Kubernetes
Containerize a Python status API and roll it out on local Kubernetes.
Introduction
30 Second Summary
Software can work perfectly on one computer and fail as soon as it moves somewhere else. A reliable delivery path keeps the result predictable wherever it runs.
In this project, you will build a Python status API that reports its health plus its release version as JSON. That service follows a complete delivery path from a Docker container into Kubernetes before a rolling update changes 1.0.0 to 1.1.0.
What You'll Build
You will refresh your browser to see a healthy API move from release 1.0.0 to 1.1.0 after Kubernetes completes the rollout.
By the end of this project, you'll have:
- A local container demo at http://localhost:8000/ that reports a healthy 1.0.0 release as JSON.
- A visible recovery story where you diagnose a blocked Pod before loading its missing image into minikube.
- A traceable release that shows 1.1.0 in the browser plus the matching change in Git history.
- Secret Mission: Scale the Kubernetes Deployment to three healthy replicas behind the same Service address.
Are there any prerequisites?
This project requires Windows 11 with administrator access plus an internet connection. Your computer needs at least 8 GB of RAM, 2 CPUs, 20 GB of free disk space, and hardware virtualization enabled.
No prior experience with Python, Docker, Git, or Kubernetes is required.
Before We Start
Before any hands-on work begins, this is your moment to commit to building a Python deployment-status API. You will use Docker for a consistent runtime, then Kubernetes for repeatable deployment and scaling.
Prepare Your Windows DevOps Workstation
Your Python status API cannot move from source code to a container or a local Kubernetes cluster until Windows has a verified editor, version control system, Linux-container runtime, and local cluster toolchain.
You will prepare Visual Studio Code 1.136.1, Git for Windows 2.55.0, WSL 2, Docker Desktop 4.89.0, and minikube 1.38.1. A fresh Windows PowerShell check confirms that every part of the toolchain is ready.
In this step, get ready to:
- Confirm that your Windows computer meets the hardware requirements.
- Prepare the editor and source control tools.
- Prepare the Linux-container runtime and local Kubernetes toolchain.
Check your hardware and install Visual Studio Code
Minikube needs enough processing power and disk space to run a local cluster. Hardware virtualization lets Docker Desktop provide the Linux environment that cluster depends on.
- Press Ctrl+Shift+Esc to open Task Manager.
- Select Performance in the left pane.
- Select CPU.
- Find Logical processors and confirm the value is at least 2.
- Confirm that Virtualization shows Enabled.
- Press the Windows key to open search.
- Type Storage settings and press Enter.
- Confirm that the Windows system drive has at least 20 GB of free space.
Does a hardware check fall short?
- Follow your computer manufacturer's instructions to enable virtualization in its UEFI or BIOS settings.
- Free enough space on the Windows system drive to reach at least 20 GB.
- Pause this project if your computer has fewer than 2 logical processors.
Ask for help with checking the Windows hardware requirements.
Visual Studio Code gives you one place to edit the API and its deployment files. The recommended User setup also adds the code command to your Windows path.
- Check whether Visual Studio Code already meets the required version by running:
code --version
What does this check show?
The command prints the installed Visual Studio Code version. It also confirms that PowerShell can find the editor from your Windows path.
✔️ I see the required version
Your installed version is 1.136.1 or higher. Your editor already meets this project's requirement.
ⓧ I see an older version
- Visit the official Visual Studio Code Windows setup guide.
- Download the User setup installer for version 1.136.1.
- Run the downloaded installer.
- Follow the installer prompts to update Visual Studio Code.
ⓧ Command not found
- Visit the official Visual Studio Code Windows setup guide.
- Download the User setup installer for version 1.136.1.
- Run the downloaded installer.
- Follow the installer prompts to install Visual Studio Code for your Windows user.
- Close every open PowerShell window.
- Press the Windows key to open search.
- Type PowerShell and press Enter.
- Verify the editor version and open the current folder by running:
code --version
code .
What do these commands prove?
- The first command confirms that version 1.136.1 or higher is installed.
- The second command proves that PowerShell can launch Visual Studio Code.
You should see the required version in PowerShell. You should also see Visual Studio Code open the current folder.
Install Git and WSL 2
Git records snapshots of your source code as the API changes. WSL provides the Linux foundation that Docker Desktop uses on Windows.
- Check whether Git is already available by running:
git --version
What does this check show?
Git prints its installed version when PowerShell can find it. This gives you a clear path for installing or updating the tool.
✔️ I see the required version
Git version 2.55.0 or higher is ready for the source snapshots you create later.
ⓧ I see an older version
- Visit the official Git for Windows installation page.
- Download Git for Windows version 2.55.0.
- Run the installer to update your existing copy.
- Keep the installer defaults for this project.
ⓧ Command not found
- Install Git for Windows by running:
winget install --id Git.Git -e --source winget
What does this command do?
Windows Package Manager finds the exact Git for Windows package in the winget source. It installs the current release for you.
Git installation failed?
Confirm that your internet connection is active. Then ask for help with the Git installation.
- Close every open PowerShell window.
- Open a fresh PowerShell window through Windows search.
- Confirm the Git installation by running:
git --version
What should Git report?
Git prints a version beginning with 2.55.0. This confirms that source control is available from a fresh PowerShell session.
- Check whether WSL already meets Docker Desktop's requirement by running:
wsl --version
Why check WSL now?
Docker Desktop's WSL 2 backend requires WSL version 2.1.5 or later. Version details confirm that the modern WSL package is installed.
✔️ I see the required version
Your WSL version is 2.1.5 or higher. The Linux foundation for Docker Desktop is ready.
ⓧ I see an older version
- Start a PowerShell window with administrator access through Windows search.
- Update WSL by running:
wsl --update
What does this command do?
Windows downloads the current WSL update. The update brings the Linux compatibility layer up to Docker Desktop's requirement.
WSL update failed?
Confirm that the PowerShell window has administrator access. Then ask for help with the WSL update.
ⓧ Command not found
- Start a PowerShell window with administrator access through Windows search.
- Install WSL by running this command. Windows may ask you to restart:
wsl --install
What does this command do?
Windows installs WSL and its required components. A restart completes the operating-system changes when Windows requests one.
WSL installation failed?
Confirm that the PowerShell window has administrator access. Then ask for help with the WSL installation.
A restart after WSL setup is normal. Windows uses it to finish enabling the required operating-system components.
- Restart Windows if prompted.
- Return to this step after Windows restarts.
- Open a fresh PowerShell window through Windows search.
- Confirm that WSL now prints version details by running:
wsl --version
What should WSL report?
You should see WSL version details instead of a missing-command response. The installed version should meet or exceed 2.1.5.
Your editor, source control system, and Linux compatibility layer are now available from PowerShell. That completes the foundation for the container tools.
Install Docker Desktop and minikube
Docker Desktop runs Linux containers on your Windows computer. Minikube uses that runtime to create the local Kubernetes cluster you deploy to later.
Why minikube with Docker?
Minikube provides a documented workflow for loading local images into a cluster. The Docker driver also makes image availability visible when you deploy the API.
- Check whether Docker Desktop already meets the required version by running:
docker desktop version
What does this check show?
The Docker Desktop CLI prints its version information. Use the result to choose the setup path that matches your computer.
✔️ I see the required version
Docker Desktop version 4.89.0 or higher is installed. You can continue to its first startup.
ⓧ I see an older version
- Visit the official Docker Desktop Windows installation guide.
- Download Docker Desktop version 4.89.0 for Windows.
- Run the downloaded installer in the recommended per-user mode.
- Keep the WSL 2 backend selected.
ⓧ Command not found
- Visit the official Docker Desktop Windows installation guide.
- Download Docker Desktop version 4.89.0 for Windows.
- Run the downloaded installer in the recommended per-user mode.
- Keep the WSL 2 backend selected.
Docker Desktop shows its terms during the first launch. Personal use and education are free under the current licensing terms.
- Press the Windows key to open search.
- Type Docker Desktop and press Enter.
- Review the Docker Desktop terms.
- Accept the terms if they apply to your use.
The first startup can take a few minutes while Docker initializes its Linux backend. That wait is expected on a fresh installation.
- Wait for Docker Desktop to finish starting.
- Verify its version and Linux-container mode by running:
docker desktop version
docker info --format '{{.OSType}}'
What do these Docker checks prove?
- The first command confirms that Docker Desktop 4.89.0 or higher is installed.
- The second command prints linux when Docker Desktop is using Linux containers.
Docker is not ready?
- Return to Docker Desktop if the command cannot connect to its engine.
- Wait for Docker Desktop to finish starting before repeating the check.
- Confirm that the WSL 2 backend is selected if Docker reports a different container type.
Ask for help with getting Docker Desktop to report Linux containers.
- Check whether minikube already meets the required version by running:
minikube version
What does this check show?
Minikube prints its installed version when PowerShell can find it. Minikube also provides a kubectl client that matches the cluster you create later.
✔️ I see the required version
Minikube version v1.38.1 or higher is ready. Your local Kubernetes command-line toolchain is installed.
ⓧ I see an older version
- Update minikube through Windows Package Manager by running:
winget upgrade --id Kubernetes.minikube
What does this command do?
Windows Package Manager targets the installed minikube package by its application ID. It upgrades that package to the current available release.
Minikube update failed?
Confirm that your internet connection is active. Then ask for help with the minikube update.
ⓧ Command not found
- Install minikube through Windows Package Manager by running:
winget install Kubernetes.minikube
What does this command do?
Windows Package Manager downloads the stable minikube package. It installs the minikube command for use from PowerShell.
Minikube installation failed?
Confirm that your internet connection is active. Then ask for help with the minikube installation.
- Close every open PowerShell window.
- Open a fresh PowerShell window through Windows search.
Before you run these checks, do you expect every tool to respond successfully from this fresh PowerShell window?
- Verify the complete workstation toolchain by running:
git --version
code .
wsl --version
docker info --format '{{.OSType}}'
minikube version
What should you see?
- Git prints a version beginning with 2.55.0.
- Visual Studio Code opens the current folder.
- WSL prints its version details.
- Docker prints linux.
- Minikube prints version v1.38.1.
Does a final check fail?
- Close PowerShell after any installation that changed your Windows path.
- Start a fresh PowerShell window before repeating the checks.
- Return to Docker Desktop if the Docker check cannot connect to its engine.
Ask for help with diagnosing the failed workstation check.
That is the workstation ready. Next, you will turn your Python status API into a running Docker container.
Build and Containerize the Status API
Your Windows workstation is ready. You can now turn that toolchain into a working service.
Running the API from a Docker container image gives you an immediate browser result. The same image can later move into Kubernetes with a consistent Python runtime.
A Git snapshot records the exact source behind the first release. That snapshot gives the later update a clear starting point.
In this step, get ready to:
- Create the Python API and its container configuration files.
- Build the tagged image and verify its JSON response in a browser.
- Save version 1.0.0 as the first Git snapshot.
Create the API and container files
The Python standard library includes http.server, which is enough for this local learning workload. The API uses it to return JSON without installing Python or extra packages on Windows.
- Switch back to the fresh PowerShell window from the previous step.
- Create the deployment-status-api folder on your Desktop by running these commands:
Set-Location -Path "$HOME\Desktop"
New-Item -Name .\deployment-status-api -ItemType Directory
Set-Location -Path .\deployment-status-api
code .
What do these commands do?
- The first command moves PowerShell to your Desktop.
- The second command creates the deployment-status-api folder.
- The third command moves PowerShell into that new folder.
- The final command opens the current folder in Visual Studio Code.
Did the folder fail to open?
Check that the folder name is exactly deployment-status-api. A spelling difference can leave PowerShell in the wrong location.
If the folder already exists, move into that existing folder before running the final command. Help me open my deployment-status-api folder from PowerShell.
- Click the New File button in the Visual Studio Code Explorer sidebar.
- Enter app.py as the file name.
- Add the API imports and response helper by pasting this first section:
import http.server
import json
HOST = "0.0.0.0"
PORT = 8000
APP_VERSION = "1.0.0"
class StatusHandler(http.server.BaseHTTPRequestHandler):
def send_json(self, status_code, payload):
body = json.dumps(payload, indent=2).encode("utf-8")
self.send_response(status_code)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
What does this code do?
- The imports provide the web server and JSON encoder.
- The constants hold the listening address, port, and displayed release.
- The send_json() helper converts a response payload into JSON bytes.
- The response headers tell the browser how to interpret those bytes.
- Save app.py from the File menu.
- Confirm that app.py appears in the Explorer sidebar.
Is the file missing?
Check that you created app.py inside the deployment-status-api folder. A file created outside this folder does not enter the Docker build context.
Use this prompt if the file appears in a different folder. Help me move app.py into the correct project folder.
- Place your cursor after the self.wfile.write(body) line.
- Add the request routes by pasting this code directly below that line:
def do_GET(self):
if self.path == "/health":
self.send_json(200, {"status": "healthy"})
return
if self.path == "/":
self.send_json(
200,
{
"service": "deployment-status-api",
"status": "healthy",
"version": APP_VERSION,
"message": "Running through Docker and Kubernetes",
},
)
return
self.send_json(404, {"status": "not-found"})
How do the routes work?
- The /health path returns a small health response for later probes.
- The / path returns the service name, status, release, and runtime message.
- Every other path receives a not-found response.
- Save app.py from the File menu.
- Confirm that do_GET() aligns with send_json() inside StatusHandler.
Does the route indentation look uneven?
Keep four spaces before both method definitions. Keep the route conditions indented inside do_GET().
If the nesting still looks unclear, use this prompt. Help me check the indentation in StatusHandler.
- Move your cursor to the end of app.py.
- Add the server startup block by pasting this final section:
if __name__ == "__main__":
server = http.server.ThreadingHTTPServer((HOST, PORT), StatusHandler)
print(f"Deployment status API listening on port {PORT}")
server.serve_forever()
How does the server start?
- The main guard starts the server only when app.py runs directly.
- The server listens on every container network interface through port 8000.
- The final line keeps the server available for incoming requests.
- Save app.py from the File menu.
- Confirm that the startup block begins at the left edge of the file.
Is the startup block inside the class?
Remove any spaces before the final if __name__ line. The startup block belongs outside StatusHandler.
Use this prompt if you need an indentation check. Help me place the startup block outside StatusHandler.
- Click the New File button in the Explorer sidebar.
- Enter Dockerfile as the file name.
- Define the container build by pasting this code:
FROM python:3.14.7-slim
WORKDIR /app
COPY app.py .
EXPOSE 8000
CMD ["python", "app.py"]
What does the Dockerfile do?
- The base image supplies Python 3.14.7 inside the container.
- The working directory gives the application a predictable location.
- The copy instruction places app.py inside the image.
- The final instructions document port 8000 and start the API.
- Save Dockerfile from the File menu.
- Confirm that Dockerfile has no file extension in the Explorer sidebar.
Does Dockerfile have an extension?
Rename the file to exactly Dockerfile if Visual Studio Code added another suffix. Docker looks for this exact file name during the build.
Use this prompt if the file name still looks wrong. Help me correct my Dockerfile name.
- Click the New File button in the Explorer sidebar.
- Enter .dockerignore as the file name.
- Exclude repository metadata and Python cache files by pasting:
.git
__pycache__
*.pyc
What does this file exclude?
The build context skips Git metadata and generated Python cache files. This keeps those local files out of the image build.
- Save .dockerignore from the File menu.
- Confirm that the Explorer sidebar lists app.py, Dockerfile, and .dockerignore.
✔️ Awesome, I've got everything!
Your three files are ready. Save each file before moving to the image build.
ⓧ I'd like to double check the full code
import http.server
import json
HOST = "0.0.0.0"
PORT = 8000
APP_VERSION = "1.0.0"
class StatusHandler(http.server.BaseHTTPRequestHandler):
def send_json(self, status_code, payload):
body = json.dumps(payload, indent=2).encode("utf-8")
self.send_response(status_code)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
if self.path == "/health":
self.send_json(200, {"status": "healthy"})
return
if self.path == "/":
self.send_json(
200,
{
"service": "deployment-status-api",
"status": "healthy",
"version": APP_VERSION,
"message": "Running through Docker and Kubernetes",
},
)
return
self.send_json(404, {"status": "not-found"})
if __name__ == "__main__":
server = http.server.ThreadingHTTPServer((HOST, PORT), StatusHandler)
print(f"Deployment status API listening on port {PORT}")
server.serve_forever()
Check app.py
Compare the imports, constants, handler methods, route indentation, and startup block with your saved file.
FROM python:3.14.7-slim
WORKDIR /app
COPY app.py .
EXPOSE 8000
CMD ["python", "app.py"]
Check Dockerfile
Compare all five instructions with your saved file. Keep the file name free of extensions.
.git
__pycache__
*.pyc
Check .dockerignore
Compare all three exclusion patterns with your saved file. Each pattern belongs on its own line.
Build and test the image
The Dockerfile turns your source into a reproducible image tagged as release 1.0.0. The first build can take a few minutes while Docker downloads the Python base image.
- Switch back to the PowerShell window in your deployment-status-api folder.
- Build the release image by running:
docker build -t deployment-status-api:1.0.0 .
What does this command do?
Docker reads the current folder as the build context. It stores the finished image under the deployment-status-api:1.0.0 tag.
- Wait for the build to return control to the PowerShell prompt.
- Confirm that the final build output contains no error.
Did the image build fail?
Confirm that PowerShell is inside the deployment-status-api folder. Docker must find Dockerfile and app.py there.
Check that Docker Desktop from the previous step is still running. Help me diagnose my Docker image build.
Before you run the container, pause to predict what proof would show that the API is serving requests from the image.
- Start the API container by running:
docker run --rm -d --name deployment-status-api -p 127.0.0.1:8000:8000 deployment-status-api:1.0.0
What does this command do?
- The container runs in the background under the name deployment-status-api.
- The published port maps Windows port 8000 to the API port inside the container.
- The temporary container removes itself after it stops.
- Open your preferred web browser.
- Enter http://localhost:8000/ in the address bar.
You should see JSON containing "service": "deployment-status-api", "status": "healthy", and "version": "1.0.0".
That is your first visible delivery win. Your API now runs from a tagged image without a separate Python installation on Windows.
Does the browser fail to connect?
Confirm that the container command returned an identifier. Check that another program is not already using port 8000.
Compare the port value in app.py with the published container port. Help me diagnose the local API connection.
- Return to the PowerShell window from earlier.
- Stop the tested container by running:
docker stop deployment-status-api
What remains after the container stops?
PowerShell prints the container name when the stop succeeds. The temporary container disappears while the tagged image remains available for future runs.
Capture the first Git snapshot
A snapshot ties the working browser result to the source files that produced it. The commit message identifies this baseline as release 1.0.0.
- Initialize the repository on the main branch by running:
git init -b main
What does this command do?
Git creates repository metadata inside deployment-status-api. The first branch starts with the name main.
- Stage all three project files by running:
git add .
What does staging do?
Staging selects the current project files for the next snapshot. The files remain editable after the commit.
- Create the first source snapshot by running:
git commit -m "Build version 1.0.0 status API"
What does this commit record?
The commit preserves the API source and both container configuration files. Its message identifies the tested release.
✔️ The commit succeeds
Your commit summary includes Build version 1.0.0 status API. The repository now has its first snapshot.
ⓧ Git requests my identity
Git needs a display name and email before it can attach an author to the snapshot. Replace the sample details with your real details before running each command.
- Set your Git display name by running:
git config --global user.name "Your Name"
What does this setting store?
Git uses this display name as the author on future commits created by your Windows account.
- Set your Git email address by running:
git config --global user.email "you@example.com"
What does this setting store?
Git pairs this email address with your author name on future commits.
- Repeat the commit after saving your identity by running:
git commit -m "Build version 1.0.0 status API"
Why repeat the commit?
The first attempt stopped before creating a snapshot. Repeating it records the staged files with your configured identity.
Before you check the history, pause to predict which release message should appear in the repository.
- Inspect the commit history by running:
git log --pretty=oneline
What does the history prove?
The one-line history connects a commit identifier to your release message. This gives the working 1.0.0 source a traceable snapshot.
You should see one history entry containing Build version 1.0.0 status API.
Is the commit missing from history?
Return to the commit outcome above if Git requested your identity. Confirm that the repeated commit completed before checking the history again.
Use this prompt if the history is still empty. Help me find my missing Git commit.
Your API now has a working container image and a traceable source snapshot. Next, you will send its Kubernetes manifests to your local cluster.
Deploy Before Loading the Image
Your API already runs from a Docker image on Windows. A Kubernetes cluster must resolve the exact image named by a Deployment before it can start a workload.
This step tests that boundary inside a new minikube cluster. You will define the desired state before inspecting the first Pod.
In this step, get ready to:
- Start a local minikube cluster with the Docker driver.
- Apply a Deployment and a Service from Kubernetes manifests.
- Inspect the first Pod to test whether it can start.
Use a local control plane
Minikube creates a local Kubernetes control plane for this project. Its Docker driver runs the cluster through Docker Desktop.
- Switch back to PowerShell from earlier.
- Create the minikube cluster with the Docker driver by running this command:
minikube start --driver=docker
What does this command do?
The command starts a local Kubernetes cluster through Docker Desktop. Minikube also prepares a compatible kubectl client for the cluster.
- Confirm that the control plane is responding by running this command:
minikube kubectl -- get po -A
What should you see?
You should see the cluster's system Pods listed in the terminal. This output proves that kubectl can communicate with the local control plane.
Cluster not responding?
- Confirm that Docker Desktop is still running with Linux containers.
- Wait for the cluster start command to finish before checking the system Pods.
Ask for help with the cluster startup output.
Your local control plane is online. It is ready to receive the API's desired state.
Describe the cluster workload
A manifest records the state that Kubernetes should maintain. The Deployment describes the API Pods while the Service gives those Pods one stable network endpoint.
- In the file sidebar of Visual Studio Code, create a folder named k8s inside the open deployment-status-api folder.
- Inside k8s, create a file named deployment.yaml.
You should see deployment.yaml nested under k8s in the file sidebar.
- Add the Deployment identity and Pod labels to k8s/deployment.yaml by pasting this first section:
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-status-api
labels:
app: deployment-status-api
spec:
replicas: 1
selector:
matchLabels:
app: deployment-status-api
template:
metadata:
labels:
app: deployment-status-api
What does this section define?
- The metadata gives the Deployment the project name.
- The replica count requests one Pod.
- The matching labels connect the Deployment selector to its Pod template.
- Save k8s/deployment.yaml.
- Confirm that the file sidebar still shows deployment.yaml under k8s.
Does the first section look misaligned?
Check that each nested YAML level uses spaces. Keep the selector labels identical to the Pod template labels.
Ask for help checking the Deployment indentation.
- Add the container and health settings by pasting this directly below the existing labels block:
spec:
containers:
- name: api
image: deployment-status-api:1.0.0
imagePullPolicy: Never
ports:
- containerPort: 8000
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 2
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
What do these settings control?
- The container requests the local image tagged deployment-status-api:1.0.0.
- The imagePullPolicy: Never setting tells Kubernetes to use an image already present in the cluster runtime.
- The readiness probe controls when the Pod can receive Service traffic.
- The liveness probe checks whether Kubernetes should restart the container.
- Save k8s/deployment.yaml.
- Apply the Deployment manifest by running this command in PowerShell:
minikube kubectl -- apply -f k8s\deployment.yaml
What does this command do?
Kubectl sends the manifest to the cluster. You should see confirmation that the Deployment was created.
Deployment manifest rejected?
- Confirm that PowerShell is still working inside the deployment-status-api folder.
- Compare the indentation beneath template with the complete file below.
Ask for help with the manifest error.
- Inside the existing k8s folder, create a file named service.yaml.
You should now see both manifest files listed inside k8s.
- Define the stable Service endpoint by pasting this into k8s/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: deployment-status-api
spec:
type: ClusterIP
selector:
app: deployment-status-api
ports:
- protocol: TCP
port: 80
targetPort: 8000
How does the Service find the API?
The selector targets Pods carrying the app: deployment-status-api label. The ClusterIP Service receives traffic on port 80 before sending it to container port 8000.
- Save k8s/service.yaml.
- Apply the Service manifest by running this command in PowerShell:
minikube kubectl -- apply -f k8s\service.yaml
What does this command do?
Kubectl creates the Service from the saved manifest. You should see confirmation that the Service was created.
Service manifest rejected?
- Confirm that service.yaml is saved inside the k8s folder.
- Check that the Service selector exactly matches app: deployment-status-api.
Ask for help checking the Service manifest.
The comparison below shows the complete project state after both manifests are applied.
✔️ Awesome, I've got everything!
Great, your application files remain intact. The new Kubernetes manifests now describe the version 1.0.0 workload.
ⓧ I'd like to double check the full code
import http.server
import json
HOST = "0.0.0.0"
PORT = 8000
APP_VERSION = "1.0.0"
class StatusHandler(http.server.BaseHTTPRequestHandler):
def send_json(self, status_code, payload):
body = json.dumps(payload, indent=2).encode("utf-8")
self.send_response(status_code)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
if self.path == "/health":
self.send_json(200, {"status": "healthy"})
return
if self.path == "/":
self.send_json(
200,
{
"service": "deployment-status-api",
"status": "healthy",
"version": APP_VERSION,
"message": "Running through Docker and Kubernetes",
},
)
return
self.send_json(404, {"status": "not-found"})
if __name__ == "__main__":
server = http.server.ThreadingHTTPServer((HOST, PORT), StatusHandler)
print(f"Deployment status API listening on port {PORT}")
server.serve_forever()
Why does app.py stay unchanged?
The API still reports version 1.0.0. This step changes how the application is deployed.
FROM python:3.14.7-slim
WORKDIR /app
COPY app.py .
EXPOSE 8000
CMD ["python", "app.py"]
Why does the Dockerfile stay unchanged?
The existing Dockerfile already produces the image requested by the Deployment. No new container build is needed in this step.
.git
__pycache__
*.pyc
Why does the ignore file stay unchanged?
The same Git metadata and Python cache exclusions still apply to future image builds.
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-status-api
labels:
app: deployment-status-api
spec:
replicas: 1
selector:
matchLabels:
app: deployment-status-api
template:
metadata:
labels:
app: deployment-status-api
spec:
containers:
- name: api
image: deployment-status-api:1.0.0
imagePullPolicy: Never
ports:
- containerPort: 8000
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 2
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
What should the Deployment contain?
The complete manifest requests one API Pod from image version 1.0.0. It also defines readiness and liveness checks against /health.
apiVersion: v1
kind: Service
metadata:
name: deployment-status-api
spec:
type: ClusterIP
selector:
app: deployment-status-api
ports:
- protocol: TCP
port: 80
targetPort: 8000
What should the Service contain?
The complete Service manifest selects the API Pods. It maps Service port 80 to application port 8000.
Inspect the blocked Pod
Before you check, do you think the Pod can reach Running with the image that currently exists on the Windows host?
- Inspect the Pod's readiness and current state by running this command:
minikube kubectl -- get pods
This blocked Pod is intentional
You should see the Pod at 0/1 readiness. It does not settle into Running.
The Deployment requests deployment-status-api:1.0.0 with imagePullPolicy: Never. The image exists on the Windows host while the minikube runtime cannot access it.
You have exposed the image-availability gap that Kubernetes operators must solve. Next, you will place the image inside minikube and bring the API online.
Load the Image and Reach the Service
Your first Kubernetes launch proved that the manifests reach the cluster. The Pod stayed blocked because its image exists only in Docker on Windows.
Minikube uses its own container image store. The cluster needs a copy of the host image before it can start the API.
This step transfers the tagged image into the cluster. A PowerShell port-forward through the Service then makes the healthy API reachable from your browser.
In this step, get ready to:
- Load the deployment-status-api:1.0.0 image into minikube.
- Restart the Deployment to create a healthy Pod.
- Reach the API through the Service on local port 8080.
Load the image into minikube
The cluster node needs its own copy of the image named in the Deployment. Minikube can transfer that image directly from Docker Desktop.
- Copy the tagged host image into the cluster runtime by running this command:
minikube image load deployment-status-api:1.0.0
What does this command do?
- The minikube image load command transfers an image into minikube.
- The deployment-status-api:1.0.0 tag matches the image requested by the Deployment.
- The existing imagePullPolicy: Never setting tells Kubernetes to use this local copy.
That clears the first blocker. PowerShell returns to its prompt after the cluster receives the image.
Image transfer did not finish?
- Confirm Docker Desktop is still running.
- Wait for Docker Desktop to become responsive.
- Retry the image load command.
Ask for help diagnosing the image transfer.
Restart the Deployment
The blocked Pod was created before its image existed in the cluster. Restarting the Deployment replaces that Pod with a fresh one that can find the image.
- Force Kubernetes to replace the blocked Pod by running this command:
minikube kubectl -- rollout restart deployment/deployment-status-api
What does this command do?
- The rollout restart command asks the Deployment controller to replace its current Pod.
- The replacement Pod starts from the image that you loaded into minikube.
The restart request is now active. Kubernetes begins replacing the blocked Pod.
Before you check the rollout, do you expect the replacement Pod to become ready now that its image is available?
- Watch the Deployment until the new Pod becomes ready by running this command:
minikube kubectl -- rollout status deployment/deployment-status-api
What does this command do?
- The rollout status command watches the Deployment progress.
- It finishes when the replacement Pod passes its readiness checks.
You will see the rollout report success. That closes the image-availability gap because the Deployment now has one healthy Pod running release 1.0.0.
Rollout still waiting?
- Confirm the image load command finished without an error.
- Confirm Docker Desktop remains running.
- Rerun the rollout status command after the replacement Pod starts.
Ask for help with a rollout that does not complete.
Reach the API through the Service
A ClusterIP Service gives the Pod a stable address inside the cluster. Port-forwarding bridges local port 8080 to Service port 80 while the command remains active.
- Press the Windows key to open Windows Search.
- Type PowerShell into the search box.
- Press Enter to start a second PowerShell window.
The port-forward keeps this second window busy while it serves browser requests. Keep the window open throughout the verification.
- Map local port 8080 to Service port 80 by running this command:
minikube kubectl -- port-forward service/deployment-status-api 8080:80
What does this command do?
- The port-forward command creates a temporary connection from Windows to the Service.
- The 8080:80 mapping sends requests from local port 8080 to Service port 80.
- The Service forwards each request to the API container on target port 8000.
- Leave the port-forward PowerShell window running.
Port-forward did not stay active?
- Confirm the rollout window reported success.
- Close any other process using local port 8080.
- Retry the port-forward command in the second PowerShell window.
Ask for help fixing the Service connection.
Before you open the API, which release do you expect Kubernetes to serve through the Service?
- Open your web browser from the Windows taskbar.
- Enter http://localhost:8080/ in the browser address bar.
You will see JSON containing "service": "deployment-status-api", "status": "healthy", and "version": "1.0.0".
- Replace the browser address with http://localhost:8080/health.
You will see healthy JSON containing "status": "healthy". Your Service now reaches a ready API Pod inside Kubernetes.
Your API is now healthy inside Kubernetes and reachable through one stable Service address. Next up, you will rebuild the image as release 1.1.0 and watch Kubernetes roll out the update.
Ship Version 1.1.0
Your 1.0.0 service is healthy behind its Kubernetes Service. Your next goal is to turn a source change into a release you can trace.
A fresh Docker image tag keeps the old image intact. Updating the Deployment template triggers a rolling update. A Git commit records exactly what changed.
In this step, get ready to:
- Build the API as image version 1.1.0.
- Update the Deployment to request the new image.
- Verify the rollout through the existing Service.
Build the new application image
The value in APP_VERSION appears in the API response. Changing that value gives the new release a visible identity.
- In the app.py tab from earlier, find this line:
APP_VERSION = "1.0.0"
What are you locating?
This constant supplies the version returned by the root endpoint. It still identifies the running release as 1.0.0.
- Replace the line with the new release value shown below:
APP_VERSION = "1.1.0"
Why change the application version?
The API now identifies its source as release 1.1.0. This gives you a visible way to prove which image Kubernetes serves.
- Save app.py.
- Build the new tagged image from the saved source by running:
docker build -t deployment-status-api:1.1.0 .
What does this command do?
- The build reads the existing Dockerfile to package the updated application.
- The tag deployment-status-api:1.1.0 preserves the release identity when the image moves into minikube.
Good progress. Docker now has a separate 1.1.0 image built from your updated source.
Image build not completing?
Confirm Docker Desktop is still running. Check that app.py was saved before you started the build.
Help me diagnose why my Docker image build is failing.
Point Kubernetes at the new image
The Deployment manifest declares the image each Pod must run. A new tag changes the Pod template that Kubernetes uses for the rollout.
- In the k8s/deployment.yaml tab from earlier, find this line:
image: deployment-status-api:1.0.0
What does this line control?
This line names the exact container image requested by each Pod. The Deployment still points to release 1.0.0.
- Replace the image line with the new tagged image shown below:
image: deployment-status-api:1.1.0
Why does the tag trigger a rollout?
The container image belongs to the Deployment's Pod template. Changing it causes Kubernetes to create a new ReplicaSet for the updated release.
- Save k8s/deployment.yaml.
- Transfer the new host image into the existing minikube cluster by running:
minikube image load deployment-status-api:1.1.0
What does this command do?
This copies the tagged image into minikube's container runtime. The cluster can now satisfy the Deployment's local image request.
You should see the command finish without reporting a load failure. The cluster now has both release images available.
Image not loading?
Confirm the deployment-status-api:1.1.0 build completed. Confirm the existing minikube cluster is still running.
Help me diagnose why minikube cannot load my local image.
- Apply the updated Deployment manifest by running:
minikube kubectl -- apply -f k8s\deployment.yaml
What does this command do?
- The command submits the desired Deployment state from k8s/deployment.yaml.
- Kubernetes compares the new Pod template with the running template. The image change starts the rolling update.
You should see confirmation that the Deployment was updated. Kubernetes can now replace the old Pod with one that runs deployment-status-api:1.1.0.
Manifest not applying?
Confirm PowerShell is still inside the deployment-status-api folder. Check the indentation around the edited image line.
Help me find the problem in my Kubernetes Deployment manifest.
✔️ Awesome, I've got everything!
Your edited files are saved with release 1.1.0 in both version references.
ⓧ I'd like to double check the full code
.git
__pycache__
*.pyc
FROM python:3.14.7-slim
WORKDIR /app
COPY app.py .
EXPOSE 8000
CMD ["python", "app.py"]
import http.server
import json
HOST = "0.0.0.0"
PORT = 8000
APP_VERSION = "1.1.0"
class StatusHandler(http.server.BaseHTTPRequestHandler):
def send_json(self, status_code, payload):
body = json.dumps(payload, indent=2).encode("utf-8")
self.send_response(status_code)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
if self.path == "/health":
self.send_json(200, {"status": "healthy"})
return
if self.path == "/":
self.send_json(
200,
{
"service": "deployment-status-api",
"status": "healthy",
"version": APP_VERSION,
"message": "Running through Docker and Kubernetes",
},
)
return
self.send_json(404, {"status": "not-found"})
if __name__ == "__main__":
server = http.server.ThreadingHTTPServer((HOST, PORT), StatusHandler)
print(f"Deployment status API listening on port {PORT}")
server.serve_forever()
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-status-api
labels:
app: deployment-status-api
spec:
replicas: 1
selector:
matchLabels:
app: deployment-status-api
template:
metadata:
labels:
app: deployment-status-api
spec:
containers:
- name: api
image: deployment-status-api:1.1.0
imagePullPolicy: Never
ports:
- containerPort: 8000
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 2
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
apiVersion: v1
kind: Service
metadata:
name: deployment-status-api
spec:
type: ClusterIP
selector:
app: deployment-status-api
ports:
- protocol: TCP
port: 80
targetPort: 8000
Verify and record the release
The readiness probe keeps the new Pod out of Service traffic until /health responds successfully. The rollout can take a short moment while Kubernetes completes that check.
Before you check, do you expect the Deployment to finish on the old image or the new one?
- Watch the Deployment until the rolling update finishes by running:
minikube kubectl -- rollout status deployment/deployment-status-api
What does this command confirm?
The command watches the Deployment until the replacement Pod becomes ready. A successful result proves Kubernetes accepted the new image template.
You should see confirmation that the Deployment completed successfully. The ready Pod now runs image version 1.1.0.
Rollout still waiting?
Confirm the new image finished loading into minikube. Check that the manifest still uses imagePullPolicy: Never with the exact loaded tag.
Help me diagnose why my Kubernetes rollout is not completing.
A port-forward can end when Kubernetes terminates the Pod it selected. The same Service address works again after you restart that session.
- Return to the PowerShell port-forward window from earlier.
- Restart the same Service port-forward command from earlier if the session ended during the rollout.
- Refresh http://localhost:8080/ in your browser.
You should see JSON containing "service": "deployment-status-api". The response now contains "version": "1.1.0".
- Stage the release files and create its Git commit by running these commands:
git add .
git commit -m "Release version 1.1.0 to Kubernetes"
What do these commands record?
- The first command stages the updated application version and Deployment image tag.
- The second command creates a source snapshot named for the Kubernetes release.
You should see confirmation that Git created the release commit. Your repository now records both the original application and its 1.1.0 rollout.
Commit not created?
If Git requests an identity, use the identity setup from your first commit. Rerun the release commit after Git accepts those details.
Help me diagnose why Git cannot create my release commit.
That completes the release loop. Your source change is packaged as a tagged image, running through Kubernetes, visible through the Service, and recorded in Git.
Secret mission
Scale the API to Three Replicas
Turn one healthy Pod into three replicas behind the existing Service. Prove that the API keeps the same browser address while Kubernetes maintains the extra copies.
Clean Up Your Resources
Clean Up Your Resources
Because this project runs locally, your minikube cluster and Docker images do not create cloud charges in your confirmed personal learning environment. Decide whether to keep the resources ready for demos, pause the cluster, or delete them entirely.
Docker Desktop Licensing
Docker Desktop is free for personal use under its licensing terms.
Resources you used:
- Your deployment-status-api folder, including its source files and Git repository.
- The two local Docker images: deployment-status-api:1.0.0 and deployment-status-api:1.1.0.
- The local minikube cluster containing a Kubernetes Deployment, Service, three healthy Pods, and loaded image copies.
- The running Windows PowerShell port-forward that maps localhost:8080 to the Service.
Keep everything running
No action is needed. Choose this option while you are actively building or demonstrating the API.
- Leave the current PowerShell port-forward running while you use http://localhost:8080/.
- Keep the minikube cluster running so its three Pods continue serving the API.
- Retain both tagged API images in Docker Desktop for future builds or demonstrations.
- Keep the deployment-status-api folder in its current location to preserve the source files and Git history.
Pause - I'll come back to this later
Pausing preserves your cluster state while freeing resources used by the running demo.
- Stop the active port-forward by pressing Ctrl+C in its PowerShell window.
- Halt the minikube cluster while preserving its state by running this command:
minikube stop
What Does This Command Do?
This command halts the local cluster while preserving its state. Your Kubernetes resources remain available when you start the cluster again.
Your local project resources remain available for the next session.
Delete - I don't want to use this again
Deleting these resources is permanent, but the cleanup happens in a clear order. Your Git history remains intact until you remove the deployment-status-api folder in the final action.
- Stop the active port-forward by pressing Ctrl+C in its PowerShell window.
- Delete every local minikube cluster by running this command:
minikube delete --all
What Gets Deleted?
This command removes all local minikube clusters. That cleanup includes the Deployment, Service, Pods, and image copies stored inside the cluster.
- Remove both API images from Docker Desktop by running this command:
docker image rm deployment-status-api:1.0.0 deployment-status-api:1.1.0
Which Images Does This Remove?
This command removes the two tagged API images from Docker Desktop's local image store. Other local images remain untouched.
The project folder is the final resource. It also holds your Git history.
- Use the deployment-status-api folder's context menu in the VS Code Explorer sidebar to show its location in File Explorer.
- Select deployment-status-api in File Explorer.
- Use File Explorer's delete control to move the folder to the Recycle Bin.
- Empty the Recycle Bin to remove the folder permanently.
That clears the full local setup. The project resources listed above are now removed from your computer.
Nice Work!
Nice Work!
You did it! Your versioned Python status API now runs as a healthy Kubernetes service from a tagged Docker image.
You've learned how to:
- Built a versioned JSON status API using Python 3.14.7 standard-library APIs. Its / route reports release details. Its /health route confirms runtime health.
- Recorded the original application in Git. Added the 1.1.0 release as a second commit. Packaged both releases as tagged Docker images.
- Resolved an intentional image-availability failure in a Kubernetes Deployment. Used health probes to protect the workload. Kept the API reachable through a stable Service. Released 1.1.0 with a rolling update.
- Secret Mission: Scaled the Deployment to three healthy replicas behind the same Service. The browser kept the same address while Kubernetes managed all three Pods.
Ready to quiz yourself?