Deploy a Shipment Tracking API to Kubernetes
Build and deploy an ASP.NET Core REST API to a local Kubernetes cluster.
Introduction
30 Second Summary
Every time you check where your online order is, you're hitting a tracking API running on a server somewhere. Those servers need to handle thousands of requests, stay online even when things break, and scale up during busy periods.
In this project, you will build a shipment tracking API from scratch, package it into a Docker container, and deploy it to a local Kubernetes cluster that automatically scales and recovers from failures.
What You'll Build
A REST API built with ASP.NET Core that tracks shipments, containerized with Docker, and orchestrated by Kubernetes on your local machine.
By the end of this project, you'll have:
- A working .NET 10 Minimal API project with endpoints to create, retrieve, and list shipments.
- A multi-stage Dockerfile that builds and packages your API into a production-ready container image.
- A Kubernetes deployment running locally with a NodePort service, horizontal scaling, and self-healing demonstrated.
- Secret Mission: Configure health check endpoints and liveness probes so Kubernetes automatically restarts unhealthy containers.
Are there any prerequisites?
No prior experience with Docker or Kubernetes is needed. This guide walks you through installing and configuring all required tools from scratch.
Build the Shipment Tracking API
Your development environment is ready. Now it's time to build the actual API that you'll eventually containerize and deploy to Kubernetes.
You'll create a shipment tracking REST API with three endpoints: one to list all shipments, one to look up a shipment by tracking ID, and one to create new shipments. The data lives in memory since our focus is on Docker and Kubernetes, not databases.
In this step, get ready to:
- Scaffold a new .NET project using the Minimal API template.
- Write shipment tracking endpoints that handle GET and POST requests.
- Test the API locally to confirm it responds correctly.
Scaffold a new .NET project
The dotnet new webapi command creates a Minimal API project. This is the modern, lightweight approach Microsoft recommends for new APIs.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create the project and move into its folder by running these commands:
dotnet new webapi -o ShipmentTracker
cd ShipmentTracker
You should see a success message confirming the template was created, followed by a restore step.
What does this command do?
The -o ShipmentTracker flag tells dotnet to create a new folder named ShipmentTracker and place the project files inside it.
Because you ran the command without the --use-controllers flag, .NET creates a Minimal API project. Minimal APIs use simple functions to define endpoints instead of full controller classes.
Seeing a template error?
Make sure your .NET SDK is version 10.x. Run dotnet --version to confirm. If you see an older version, revisit Step 1 to install the .NET 10 SDK.
Help me fix this template error
- Open the ShipmentTracker folder in VS Code by running:
code .
VS Code opens with the project files in the Explorer sidebar.
The scaffold generated a sample weather forecast API in Program.cs. You'll replace that entirely with your shipment tracking code. First, configure the local development port.
- In the VS Code Explorer sidebar, expand the Properties folder and click launchSettings.json to open it.
- Select all the content in the file (Ctrl+A on Windows, Cmd+A on macOS) and delete it.
- Replace it with this configuration:
{
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
What does this file do?
The launchSettings.json file tells .NET which port to use when you run the app locally. You're setting it to http://localhost:5000 so you have a predictable address to test against.
The launchBrowser: false setting prevents a browser tab from automatically opening every time you start the server.
- Save launchSettings.json (Ctrl+S on Windows, Cmd+S on macOS).
Write the shipment tracking endpoints
Now you'll replace the scaffold's sample code with your shipment tracking API. The file uses ASP.NET Core Minimal APIs to define three endpoints in a single Program.cs file.
- In VS Code, click Program.cs in the Explorer sidebar to open it.
- Select all the existing content (Ctrl+A on Windows, Cmd+A on macOS) and delete it.
- Paste this first section of code, which sets up the app and defines the seed data:
// Build and configure the web application
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Seed data: pre-loaded shipments for testing
var shipments = new List<Shipment>
{
new("SHP-001", "New York", "Los Angeles", "In Transit", DateTime.UtcNow.AddHours(-12)),
new("SHP-002", "Chicago", "Miami", "Delivered", DateTime.UtcNow.AddDays(-1)),
new("SHP-003", "Seattle", "Denver", "Processing", DateTime.UtcNow.AddMinutes(-30))
};
// GET /shipments - returns all shipments
app.MapGet("/shipments", () => Results.Ok(shipments));
What does this code do?
- WebApplication.CreateBuilder(args) initializes the ASP.NET Core framework and builds the app pipeline.
- The shipments list holds three sample records in memory. This acts as your "database" for now.
- MapGet("/shipments", ...) registers a GET endpoint that returns the full list as JSON.
- Directly below the code you just pasted, add the lookup-by-ID endpoint:
// GET /shipments/{trackingId} - find a shipment by its tracking ID
app.MapGet("/shipments/{trackingId}", (string trackingId) =>
{
var shipment = shipments.FirstOrDefault(s =>
s.TrackingId.Equals(trackingId, StringComparison.OrdinalIgnoreCase));
return shipment is not null ? Results.Ok(shipment) : Results.NotFound(new { message = "Shipment not found" });
});
What does this code do?
This endpoint accepts a trackingId from the URL path and searches the list using a case-insensitive comparison. If found, it returns the shipment as JSON. If not, it returns a 404 with a helpful message.
- Below the GET endpoint you just added, paste the POST endpoint, the app.Run() call, and the record types:
// POST /shipments - create a new shipment
app.MapPost("/shipments", (CreateShipmentRequest request) =>
{
// Generate a random tracking ID
var trackingId = $"SHP-{Random.Shared.Next(100, 999)}";
var shipment = new Shipment(trackingId, request.Origin, request.Destination, "Processing", DateTime.UtcNow);
shipments.Add(shipment);
return Results.Created($"/shipments/{trackingId}", shipment);
});
// Start the web server
app.Run();
// Data models
record Shipment(string TrackingId, string Origin, string Destination, string Status, DateTime LastUpdated);
record CreateShipmentRequest(string Origin, string Destination);
What does this code do?
- MapPost("/shipments", ...) registers a POST endpoint that accepts a JSON body with Origin and Destination fields.
- It generates a random tracking ID, creates a new Shipment record, adds it to the list, and returns a 201 Created response.
- app.Run() starts the web server and begins listening for HTTP requests.
- The two record types define the shape of your data. C# records are a concise way to declare immutable data classes.
- Save Program.cs.
✔️ Awesome, I've got everything!
Great. Double check you've saved Program.cs before moving on.
ⓧ I'd like to double check the full code
Your complete Program.cs should look exactly like this:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var shipments = new List<Shipment>
{
new("SHP-001", "New York", "Los Angeles", "In Transit", DateTime.UtcNow.AddHours(-12)),
new("SHP-002", "Chicago", "Miami", "Delivered", DateTime.UtcNow.AddDays(-1)),
new("SHP-003", "Seattle", "Denver", "Processing", DateTime.UtcNow.AddMinutes(-30))
};
app.MapGet("/shipments", () => Results.Ok(shipments));
app.MapGet("/shipments/{trackingId}", (string trackingId) =>
{
var shipment = shipments.FirstOrDefault(s =>
s.TrackingId.Equals(trackingId, StringComparison.OrdinalIgnoreCase));
return shipment is not null ? Results.Ok(shipment) : Results.NotFound(new { message = "Shipment not found" });
});
app.MapPost("/shipments", (CreateShipmentRequest request) =>
{
var trackingId = $"SHP-{Random.Shared.Next(100, 999)}";
var shipment = new Shipment(trackingId, request.Origin, request.Destination, "Processing", DateTime.UtcNow);
shipments.Add(shipment);
return Results.Created($"/shipments/{trackingId}", shipment);
});
app.Run();
record Shipment(string TrackingId, string Origin, string Destination, string Status, DateTime LastUpdated);
record CreateShipmentRequest(string Origin, string Destination);
Seeing red squiggly lines in VS Code?
If VS Code shows errors, make sure you deleted ALL the original scaffold code before pasting. There should be no leftover using statements or sample code from the template.
Help me fix these errors in Program.cs
Test the API locally
Before containerizing anything, confirm the API works on its own. You'll start the server and send it a request.
- Open a terminal in VS Code by pressing Ctrl+` (backtick key, top-left of your keyboard).
- Start the API by running:
dotnet run
You should see output indicating the server is listening on http://localhost:5000.
- Open a second terminal in VS Code by clicking the + icon in the terminal panel.
- Test the API by running this command:
Invoke-RestMethod -Uri http://localhost:5000/shipments -Method Get
You should see the three seed shipments returned as a table with columns for TrackingId, Origin, Destination, Status, and LastUpdated.
What does this command do?
Invoke-RestMethod is PowerShell's built-in tool for making HTTP requests. It sends a GET request to your API and automatically formats the JSON response into a readable table.
Seeing "connection refused"?
Make sure dotnet run is still running in your first terminal. The server must be active to respond to requests.
If the port doesn't match, confirm your launchSettings.json has http://localhost:5000 as the applicationUrl.
Help me troubleshoot this connection error
- Switch back to your first terminal (the one running the server).
- Press Ctrl+C to stop the server.
Your API is working locally. Next up, you'll package it into a Docker container so it can run anywhere, not just on your machine.
Containerize with Docker
Your shipment tracking API is working locally. But right now it depends on having the .NET SDK installed on your machine.
Docker solves this by packaging your app and its runtime into a portable container image. In this step, you will write a Dockerfile, build the image, and run your API inside a container to prove it works independently.
In this step, get ready to:
- Create a multi-stage Dockerfile that compiles and packages your API.
- Build a Docker image from your project.
- Run the container and confirm the API responds.
Create the Dockerfile
A Dockerfile is a recipe that tells Docker how to build your image. You will use a multi-stage build: one stage compiles your code with the full SDK, and a second stage copies only the compiled output into a lightweight runtime image.
- In VS Code, create a new file named Dockerfile (no file extension) in the root of your ShipmentTracker folder.
- Paste the following code into the file:
# Build stage: compile the app using the full .NET SDK
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
# Copy the project file first to cache the restore step
COPY ShipmentTracker.csproj ./
RUN dotnet restore
# Copy remaining source and publish a release build
COPY . ./
RUN dotnet publish -c Release -o /app/publish
# Runtime stage: use the lightweight ASP.NET image
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
# Pull in only the compiled output from the build stage
COPY --from=build /app/publish ./
EXPOSE 8080
ENTRYPOINT ["dotnet", "ShipmentTracker.dll"]
What does this Dockerfile do?
The Dockerfile has two stages, each starting with a FROM instruction.
- Build stage (sdk:10.0): copies your project file, restores dependencies, then publishes a compiled Release build. This image is large (hundreds of MB) because it contains the full compiler toolchain.
- Runtime stage (aspnet:10.0): starts fresh from a small image that only knows how to run .NET apps. It copies the compiled output from the build stage and sets the entrypoint. Your final image stays small and production-ready.
Copying the .csproj file separately before the rest of the source lets Docker cache the dotnet restore layer. Future builds that only change code skip the restore entirely.
- Save Dockerfile.
File has a .txt extension?
Some editors automatically append .txt. Make sure the file is named exactly Dockerfile with no extension. In VS Code, right-click the file in the Explorer sidebar and select Rename to fix it.
help me fix my Dockerfile name
✔️ Awesome, I've got everything!
Great. Double check you have saved your Dockerfile.
ⓧ I'd like to double check the full code
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ShipmentTracker.csproj ./
RUN dotnet restore
COPY . ./
RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish ./
EXPOSE 8080
ENTRYPOINT ["dotnet", "ShipmentTracker.dll"]
Build the Docker image
With the Dockerfile ready, Docker can now read the recipe and produce an image. The first build downloads the base images, which takes a minute or two.
- Build your Docker image by running this command in PowerShell from the ShipmentTracker folder:
docker build -t shipment-tracker:latest .
What does this command do?
- docker build tells Docker to read the Dockerfile in the current directory and execute each instruction.
- -t shipment-tracker:latest names (tags) the resulting image so you can reference it later.
- . (the dot) tells Docker the build context is the current folder. Docker sends these files into the build.
- Verify the image was created by running:
docker images
You should see shipment-tracker listed with the tag latest. The image size should be around 200-250 MB (much smaller than the full SDK image).
Build failed with an error?
- Make sure your PowerShell prompt is inside the ShipmentTracker folder (the one containing Dockerfile and ShipmentTracker.csproj).
- If you see a restore error, confirm your ShipmentTracker.csproj file targets net10.0 and has no typos.
- Check that Docker Desktop is running. The Docker daemon must be active for builds to work.
help me debug my Docker build error
Run and test the container
An image is a blueprint. To actually run your API, you start a container from that image. Think of it like launching an app from an installer.
- Start a container from your image by running:
docker run -d -p 8080:8080 --name tracker-container shipment-tracker:latest
What does this command do?
- -d runs the container in detached mode (in the background), so you get your terminal back.
- -p 8080:8080 maps port 8080 on your machine to port 8080 inside the container. The ASP.NET runtime image listens on port 8080 by default.
- --name tracker-container gives the container a human-readable name so you can stop and remove it easily.
- Test that your containerized API responds by running:
Invoke-RestMethod -Uri http://localhost:8080/shipments -Method Get
You should see the three seed shipments returned as JSON. Your API is now running inside a Docker container, completely independent of your local .NET installation.
Getting a connection refused error?
- Run docker ps to check if the container is running. If it is not listed, run docker logs tracker-container to see what went wrong.
- If another process is using port 8080, you will see a bind error. Stop the conflicting process or change the first port number (e.g. -p 9090:8080).
- Make sure you stopped your earlier dotnet run session with Ctrl+C. It was on port 5000, but double-check nothing else grabbed 8080.
help me fix my Docker container connection issue
- Now that you have confirmed it works, stop and remove the container by running:
docker stop tracker-container
docker rm tracker-container
Why stop and remove?
In the next step, Kubernetes will manage containers for you. You need to free up the container name and port so Kubernetes can use them without conflicts.
Your API is now packaged into a portable Docker image. Next up, you will deploy this image to a Kubernetes cluster where it gets scaling and self-healing for free.
Deploy to Kubernetes
Your API is packaged inside a Docker container image. You proved it works by running a container locally. Now the question becomes: who manages that container in production?
This is where Kubernetes comes in. Instead of manually starting and stopping containers with docker run, you write a YAML manifest that declares your desired state. Kubernetes reads it and makes it happen.
In this step, get ready to:
- Create a Kubernetes manifest file that declares your deployment and service.
- Apply the manifest to your local cluster.
- Access your API through Kubernetes on port 30001.
Create the Kubernetes Deployment manifest
A Kubernetes Deployment tells the cluster how many copies of your container to run and which image to use. You define this in a YAML file that Kubernetes reads as your "desired state."
- In VS Code, create a new file called k8s-deployment.yaml in your ShipmentTracker project folder (the same folder that contains your Dockerfile).
- Paste the following Deployment definition into the file:
apiVersion: apps/v1
kind: Deployment
metadata:
name: shipment-tracker
spec:
replicas: 1
selector:
matchLabels:
app: shipment-tracker
template:
metadata:
labels:
app: shipment-tracker
spec:
containers:
- name: shipment-tracker
image: shipment-tracker:latest
ports:
- containerPort: 8080
imagePullPolicy: Never
What does this YAML do?
This manifest tells Kubernetes to create a Deployment named shipment-tracker with these instructions:
- replicas: 1 means "run exactly one copy of my container."
- image: shipment-tracker:latest points to the Docker image you built in the previous step.
- imagePullPolicy: Never tells Kubernetes to use your locally-built image instead of trying to download it from Docker Hub.
Why imagePullPolicy: Never?
Your image only exists on your machine. Without this setting, Kubernetes would try to pull it from a remote registry and fail with an ErrImagePull error.
Add the Service definition
A Kubernetes Service gives your running container a stable network address that outside traffic can reach. Without it, your pods are only accessible inside the cluster.
- Add a YAML document separator and the Service definition below the Deployment in the same file:
---
apiVersion: v1
kind: Service
metadata:
name: shipment-tracker-service
spec:
type: NodePort
selector:
app: shipment-tracker
ports:
- port: 8080
targetPort: 8080
nodePort: 30001
What does the Service do?
The --- separator lets you define multiple Kubernetes objects in one file.
- type: NodePort exposes the Service on a port that is accessible from your machine (outside the cluster).
- nodePort: 30001 is the port you will use to reach your API from a browser or PowerShell.
- selector: app: shipment-tracker routes traffic to any pod with that label (matching the Deployment template above).
- Save k8s-deployment.yaml.
YAML indentation issues?
YAML is sensitive to indentation. Every nested level uses exactly 2 spaces (not tabs). If you see errors later when applying, check that your indentation matches the code blocks above exactly.
Help me fix my YAML formatting
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before moving on.
ⓧ I'd like to double check the full code
apiVersion: apps/v1
kind: Deployment
metadata:
name: shipment-tracker
spec:
replicas: 1
selector:
matchLabels:
app: shipment-tracker
template:
metadata:
labels:
app: shipment-tracker
spec:
containers:
- name: shipment-tracker
image: shipment-tracker:latest
ports:
- containerPort: 8080
imagePullPolicy: Never
---
apiVersion: v1
kind: Service
metadata:
name: shipment-tracker-service
spec:
type: NodePort
selector:
app: shipment-tracker
ports:
- port: 8080
targetPort: 8080
nodePort: 30001
Apply the manifest to your cluster
With your manifest saved, you can hand it to Kubernetes. The kubectl apply command reads your YAML and creates (or updates) the resources it describes.
- In PowerShell (from your ShipmentTracker folder), apply the manifest by running this command:
kubectl apply -f k8s-deployment.yaml
What does kubectl apply do?
This command sends your YAML to the Kubernetes API server. Kubernetes reads the desired state you declared and creates the Deployment and Service. It will use your local Docker image and start a pod running your container.
- Verify your Deployment is running by running:
kubectl get deployments
- You should see shipment-tracker with 1/1 under the READY column. This means one pod is running and healthy.
- Verify your Service is running by running:
kubectl get services
- You should see shipment-tracker-service listed with port 8080:30001/TCP showing that traffic on port 30001 routes to your container.
Deployment not showing 1/1 READY?
If you see 0/1 under READY, the pod may still be starting. Wait 10-15 seconds and run kubectl get deployments again.
If it stays at 0/1, run kubectl get pods and check the STATUS column. An ErrImagePull or ImagePullBackOff status means Kubernetes cannot find your image. Confirm that imagePullPolicy: Never is in your YAML and that you built the image as shipment-tracker:latest.
Help me debug my Kubernetes deployment
Access your API through Kubernetes
Your API is no longer running via docker run. Kubernetes is managing the container lifecycle for you. The NodePort Service exposes it on port 30001.
- Test your Kubernetes-hosted API by running this command:
Invoke-RestMethod -Uri http://localhost:30001/shipments -Method Get
- You should see the same JSON array of three shipments you saw earlier when testing with Docker. The difference is that Kubernetes is now managing your container.
What changed from the Docker step?
When you used docker run, you were responsible for starting, stopping, and restarting the container manually. Now Kubernetes owns that job.
If your container crashes, Kubernetes restarts it automatically. If you want three copies, you change one number in your YAML. This declarative model is the core value of container orchestration.
Connection refused on port 30001?
Make sure no other process is using port 30001. Also confirm your Service is running with kubectl get services. If the Service does not appear, re-run kubectl apply -f k8s-deployment.yaml and check for YAML errors in the output.
Help me fix Kubernetes connection issues
Your API is officially running inside a Kubernetes cluster. Next up, you will see what makes Kubernetes special by scaling your deployment to multiple replicas and watching it self-heal.
Scale and Observe Self-Healing
Your API is live in Kubernetes, serving requests through a managed Service on port 30001. That's already a step up from running a raw Docker container.
In this step, you'll see why Kubernetes exists as a layer on top of Docker. You'll scale your API to multiple copies with a single command, then deliberately break things to watch Kubernetes fix them automatically.
In this step, get ready to:
- Scale your deployment to 3 replicas for horizontal scaling.
- Observe Kubernetes self-healing by deleting a pod and watching it get replaced.
- Inspect pod logs and deployment state, then clean up.
Scale your deployment to multiple replicas
Running a single copy of your API means if that copy crashes, your users see downtime. Kubernetes lets you run multiple identical copies (called replicas) and distributes traffic across them automatically.
- Scale your deployment to 3 replicas by running this command in PowerShell:
kubectl scale deployment shipment-tracker --replicas=3
What does this command do?
This tells Kubernetes to update the desired state of your deployment from 1 replica to 3. Kubernetes reads this new desired state and immediately starts spinning up 2 additional pods to match it.
This is horizontal scaling. Instead of making one pod bigger, you add more pods. Kubernetes load-balances requests across all of them through your Service.
- Verify all 3 pods are running by running:
kubectl get pods
You should see three pods listed, all with a status of Running. Each one is an independent copy of your shipment tracking API.
Not seeing 3 pods?
If one or more pods show Pending or ContainerCreating, wait a few seconds and run kubectl get pods again. Kubernetes needs a moment to start each container.
If you see ErrImagePull, your Docker image may not be available to the cluster. Make sure you built it with docker build -t shipment-tracker:latest . in the previous step.
Help me troubleshoot my pods not starting
- Confirm your API still responds through the Service by running:
Invoke-RestMethod -Uri http://localhost:30001/shipments -Method Get
You should see the same shipment data as before. Kubernetes is now routing your request to one of the three pods behind the scenes.
Watch Kubernetes self-heal
Kubernetes continuously compares actual state to desired state. Your Deployment declares 3 replicas. If the actual count drops below 3, Kubernetes immediately creates a replacement.
- List your pods and pick one to delete by running:
kubectl get pods
Copy one of the pod names from the output. It will look something like shipment-tracker-xxxxxxxxx-xxxxx.
- Delete that pod to simulate a crash by running:
kubectl delete pod [[PODNAME="the pod name you copied"]]
- Immediately check your pods again by running:
kubectl get pods
You should see three pods again. One will have a very recent age (a few seconds old). That's the replacement Kubernetes created automatically.
How does self-healing work?
The Deployment resource owns a ReplicaSet, which monitors running pod count. When you deleted a pod, the actual count dropped to 2. The ReplicaSet detected the mismatch and scheduled a new pod within seconds.
This happens automatically for any reason a pod disappears. Hardware failure, out-of-memory kill, or accidental deletion. You never manually restart containers in production Kubernetes.
Still seeing only 2 pods?
Run kubectl get pods one more time. The replacement pod typically appears within 2-5 seconds, but it may briefly show ContainerCreating before switching to Running.
Help me understand why my pod wasn't replaced
Inspect logs and clean up
Before tearing down your deployment, take a look at the diagnostic tools Kubernetes gives you for production troubleshooting.
- View application logs from one of your pods by running:
kubectl logs [[PODNAME="one of the pod names from kubectl get pods"]]
What are pod logs?
The kubectl logs command shows everything your application wrote to standard output. This is equivalent to the console output you see when running dotnet run locally, but for a container running inside the cluster.
- Inspect the full deployment state by running:
kubectl describe deployment shipment-tracker
You should see details about the deployment's replicas, strategy, pod template, and events. This is the first place to look when diagnosing deployment issues in production.
- Tear down your Kubernetes deployment and service by running:
kubectl delete -f k8s-deployment.yaml
- Confirm the resources are gone by running:
kubectl get deployments
You should see no resources listed (or a message indicating no resources found in the default namespace). Your cluster is now clean.
Getting a 'not found' error on delete?
Make sure you are running the kubectl delete command from the ShipmentTracker folder where k8s-deployment.yaml lives. Kubernetes needs to read the file to know which resources to delete.
Help me delete my Kubernetes resources
Secret mission
Health Checks and Liveness Probes
Your API is running in Kubernetes, but what happens when it becomes unhealthy? Right now, Kubernetes has no way to know if your app is silently broken. In this mission, you will add a health check endpoint and configure Kubernetes liveness probes so the cluster automatically detects and restarts unhealthy containers.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
This project runs entirely on your local machine using Docker Desktop. There are no ongoing cloud costs, but the Kubernetes cluster does use some RAM and CPU while running.
Resources you used:
- Docker image shipment-tracker:latest (stored locally).
- Kubernetes deployment and service (running in Docker Desktop).
- ShipmentTracker project folder on your Desktop.
Keep everything running
No action needed. Choose this if you are still actively building or experimenting with Kubernetes.
- Your Kubernetes cluster stays running inside Docker Desktop and uses some RAM and CPU in the background.
- Your Docker image remains in your local image store.
- Your ShipmentTracker project folder stays on your Desktop.
Pause - I'll come back to this later
Stop the Kubernetes cluster to free up memory without losing your configuration or project files.
- Open the Docker Desktop Dashboard.
- Select the Kubernetes view from the left sidebar.
- Select Stop to disable the cluster.
- Your project folder and Docker image remain on disk. When you are ready to resume, select Create cluster again from the same Kubernetes view and reapply your manifest.
Delete - I don't want to use this again
Remove all project resources and reclaim disk space.
- Delete the Kubernetes deployment and service by running this command in PowerShell from the ShipmentTracker folder:
kubectl delete -f k8s-deployment.yaml
- Remove the Docker image by running:
docker rmi shipment-tracker:latest
- Stop and remove the Kubernetes cluster from Docker Desktop.
- Open the Docker Desktop Dashboard.
- Select the Kubernetes view from the left sidebar.
- Select Stop to disable and remove the cluster.
- Delete the project folder. On macOS or Linux:
cd ~/Desktop
rm -rf ShipmentTracker
- On Windows in PowerShell:
cd ~\Desktop
Remove-Item -Recurse -Force ShipmentTracker
That's a Wrap!
That's a Wrap!
Nice work! You just built a shipment tracking API from scratch, packaged it into a Docker container, and deployed it to a live Kubernetes cluster on your own machine.
You've learned how to:
- Build a REST API with ASP.NET Core Minimal APIs that handles shipment tracking requests and stores data in memory.
- Containerize your API using a multi-stage Dockerfile, separating the build stage from the lightweight runtime stage to produce a production-ready image.
- Deploy to a local Kubernetes cluster with a declarative YAML manifest, then demonstrate horizontal scaling and self-healing by scaling replicas and deleting pods.
- Secret Mission: Configured liveness probes so Kubernetes automatically detects and restarts unhealthy containers.
Ready to quiz yourself?