Deploying to a Container and Docker Hub - Building a Sample API in C# Course
Deploying a .NET Core application into a Docker container is a crucial skill for modern developers. Containers offer immutable infrastructure, easy distribution, and lightweight runtime environments. In this in-depth guide, we’ll walk through deploying a sample API to a Docker container, and then push it to Docker Hub, following the steps demonstrated in Tim Corey’s comprehensive video tutorial: “Deploying to a Container and Docker Hub – Building a Sample API in C#.”
Let’s explore the process step-by-step, referencing specific timestamps for easy follow-along. This guide includes usage of commands like dotnet publish, the docker run command, and terms like base image, runtime image, and container image—all important concepts for deploying a .NET application using Docker.
Creating and Understanding the Sample API
At the start of the video, Tim highlights the usefulness of a sample app when learning web development. The app includes endpoints like /courses and /health, simulates real-world issues like degraded health checks, and is designed to run on both your local machine and a remote server.
This is a .NET Core app (more precisely an ASP.NET Core minimal API) that can be run and tested easily through Docker containers.
Setting Up Docker Locally
Tim checks that Docker is installed on his local machine, using Docker Desktop. He notes that it’s possible to publish directly to Docker Hub without Docker installed locally, but for demonstration, he begins with local deployment.
He shows two Docker images pre-installed:
A personal tool for subtitles
- The official ASP.NET Core base image from Microsoft: mcr.microsoft.com/dotnet/aspnet
This base image is around 328 MB and acts as the foundation for our container image. Microsoft hosts these images on their MCR (Microsoft Container Registry) and they support both Linux containers and Windows containers. For this project, Linux is the target operating system.
Publishing to a Container Locally
Tim navigates to the API project folder and runs the following command in the command line:
dotnet publish -p:PublishProfile=DefaultContainer
dotnet publish -p:PublishProfile=DefaultContainer
This command uses the built-in container support in .NET 8 and .NET 9, which removes the need for a Dockerfile. The publish process selects a suitable runtime image and builds a self-contained container image.
Here’s what happens under the hood:
The project file (.csproj) is used to build the API
Dependencies are restored using the dotnet restore command
The output goes into a publish folder
- A DLL file is produced and embedded in the container
The resulting image is about 333 MB (only 5 MB larger than the base).
Running the Image with Docker
Once the image is created, Tim uses Docker Desktop to run the container. Internally, the app listens on port 8080. Tim maps that to a random host port by entering 0 in the port configuration.
The container launches with a random name (e.g., elegant_hoover), and the browser opens to display a working API with “Hello World” as the default response.
By browsing to /courses and /health, Tim confirms that the API is functioning. The health check endpoint is useful for simulating a production environment scenario.
To view running containers, you can use the docker ps command in the CLI. Tim deletes the running container and image afterward to demonstrate cleanup.
Creating a Docker Hub Repository
Next, Tim moves to the cloud deployment portion. He logs in to his Docker Hub account and creates a public repository named thesampleapi.
This is where the Docker image will be pushed so others can pull it using:
docker pull timcorey/thesampleapi:latest
docker pull timcorey/thesampleapi:latest
Configuring the Project for Docker Hub
Inside the API’s project file, Tim adds metadata to define where and how to publish the Docker image. He adds the following code block:
<ContainerRegistry>docker.io</ContainerRegistry>
<ContainerRepository>timcorey/thesampleapi</ContainerRepository>
<ContainerImageTags>1.0.0;latest</ContainerImageTags>
<ContainerRegistry>docker.io</ContainerRegistry>
<ContainerRepository>timcorey/thesampleapi</ContainerRepository>
<ContainerImageTags>1.0.0;latest</ContainerImageTags>
ContainerRegistry: Specifies Docker Hub as the target registry.
ContainerRepository: The path to the Docker Hub repo.
- ContainerImageTags: Tags the image for versioning and latest release.
This prepares the image for a multi-stage build flow in the future, though Tim is keeping things simple for now.
Publishing to Docker Hub
Tim runs the same publish command again:
dotnet publish -p:PublishProfile=DefaultContainer
dotnet publish -p:PublishProfile=DefaultContainer
Initially, the push fails because he forgot to save the .csproj file. After saving and re-running the command, the image uploads to Docker Hub successfully.
In Docker Hub, both latest and 1.0.0 tags appear under the thesampleapi repo. These tags allow flexibility when pulling specific versions.
Pulling the Image from Docker Hub
Tim demonstrates how to pull the image from any machine with Docker installed:
docker pull timcorey/thesampleapi:latest
docker pull timcorey/thesampleapi:latest
Thanks to Docker layer caching, the image downloads quickly since most of it already exists from the base image.
To run the image:
docker run -p 0:8080 timcorey/thesampleapi:latest
docker run -p 0:8080 timcorey/thesampleapi:latest
This maps a random host port to the internal port 8080.
Creating a New Version of the Image
To demonstrate versioning, Tim edits the .csproj file to:
<ContainerImageTags>1.0.1;latest</ContainerImageTags>
<ContainerImageTags>1.0.1;latest</ContainerImageTags>
He then re-runs the publish command:
dotnet publish -p:PublishProfile=DefaultContainer
dotnet publish -p:PublishProfile=DefaultContainer
Now the Docker Hub repository shows three tags:
1.0.0
1.0.1
- latest (now pointing to 1.0.1)
Tim explains that despite multiple tags, Docker doesn’t store duplicate layers, optimizing disk usage through distinct layers.
Using the Docker Image for Local Development
Tim emphasizes that once the image is on Docker Hub, you can run it anytime on any machine by using:
docker pull timcorey/thesampleapi:latest
docker pull timcorey/thesampleapi:latest
Then use the docker run command to spin it up. When you're finished, clean up with:
docker ps # Get container ID
docker stop <id> # Stop the container
docker rm <id> # Remove the container
docker rmi <image> # Remove the image
docker ps # Get container ID
docker stop <id> # Stop the container
docker rm <id> # Remove the container
docker rmi <image> # Remove the image
He explains this makes it perfect for on-demand development and testing without bloating your local machine.
What’s Next?
Tim wraps up by mentioning the next step: deploying this same API to a Virtual Private Server (VPS). He’ll walk through mapping a domain and configuring the container to be publicly accessible.
Conclusion: From Local Build to Cloud Deployment
Using Tim Corey’s video guide, we’ve:
Built a .NET Core API using dotnet new console
Used dotnet restore and dotnet publish to build and deploy
Created a Docker container with no need for a sample Dockerfile
Uploaded the container image to Docker Hub
Pulled and ran the container using docker run
Managed tags and distinct image layers
- Prepared for production environments using versioned images
This workflow, centered around Docker and .NET, is perfect for building scalable, testable, and sharable applications.