Basic Tutorial: Containerizing Your First .NET Application
This step-by-step tutorial will guide you through containerizing a .NET application using JD.MSBuild.Containers, from a blank project to a running container.
What You'll Learn
- Creating a new .NET Web API project
- Installing and configuring JD.MSBuild.Containers
- Generating a Dockerfile automatically
- Building a Docker image
- Running your application in a container
- Testing the containerized application
Prerequisites
Ensure you have completed the Getting Started prerequisites:
- .NET SDK 8.0 or later
- Docker Desktop or compatible runtime
- Basic terminal/command line knowledge
Step 1: Create a New Web API Project
Let's start by creating a simple ASP.NET Core Web API:
# Create a new directory for your project
mkdir MyFirstContainerApp
cd MyFirstContainerApp
# Create a new Web API project
dotnet new webapi -n MyFirstContainerApp
cd MyFirstContainerApp
# Verify the project builds
dotnet build
You should see output indicating a successful build:
Build succeeded.
0 Warning(s)
0 Error(s)
Step 2: Run the Application (Without Docker)
Before containerizing, let's verify the application works:
# Run the application
dotnet run
You should see output like:
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5000
Open another terminal and test the API:
curl http://localhost:5000/weatherforecast
You should receive a JSON response with weather data. Press Ctrl+C to stop the application.
Step 3: Add JD.MSBuild.Containers
Now let's add containerization support:
# Add the package
dotnet add package JD.MSBuild.Containers
You should see:
info : PackageReference for package 'JD.MSBuild.Containers' version '*' added to file 'MyFirstContainerApp.csproj'.
Step 4: Configure Docker Settings
Open MyFirstContainerApp.csproj in your favorite editor and add Docker properties:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Docker Configuration -->
<DockerEnabled>true</DockerEnabled>
<DockerImageName>myfirstcontainerapp</DockerImageName>
<DockerImageTag>v1</DockerImageTag>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JD.MSBuild.Containers" Version="*" />
</ItemGroup>
</Project>
What These Properties Mean:
DockerEnabled- Enables Docker integrationDockerImageName- Name of your Docker image (lowercase, no spaces)DockerImageTag- Version tag for your image
Step 5: Generate the Dockerfile
Build your project to generate the Dockerfile:
dotnet build
You should see new output indicating Dockerfile generation:
Resolving Docker inputs...
Generating Dockerfile at /path/to/MyFirstContainerApp/Dockerfile
Dockerfile generated successfully
Step 6: Review the Generated Dockerfile
Let's examine what was generated:
cat Dockerfile
You should see a multi-stage Dockerfile similar to:
# This file was auto-generated by JD.MSBuild.Containers
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyFirstContainerApp.csproj", "./"]
RUN dotnet restore "MyFirstContainerApp.csproj"
COPY . .
RUN dotnet build "MyFirstContainerApp.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "MyFirstContainerApp.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyFirstContainerApp.dll"]
Understanding the Dockerfile:
- Stage 1 (base): Sets up the runtime environment
- Stage 2 (build): Compiles your application
- Stage 3 (publish): Publishes the application
- Stage 4 (final): Creates the minimal runtime image
Step 7: Build the Docker Image
Now let's build the Docker image. Update your .csproj to enable image building:
<PropertyGroup>
<DockerEnabled>true</DockerEnabled>
<DockerImageName>myfirstcontainerapp</DockerImageName>
<DockerImageTag>v1</DockerImageTag>
<!-- Enable automatic image building -->
<DockerBuildImage>true</DockerBuildImage>
<DockerBuildOnPublish>true</DockerBuildOnPublish>
</PropertyGroup>
Now publish to build the image:
dotnet publish
You should see Docker build output:
Building Docker image: myfirstcontainerapp:v1
docker build -t myfirstcontainerapp:v1 .
[+] Building 45.2s (17/17) FINISHED
=> [internal] load build definition from Dockerfile
=> [internal] load .dockerignore
=> [build] COPY ["MyFirstContainerApp.csproj", "./"]
=> [build] RUN dotnet restore
=> [build] COPY . .
=> [build] RUN dotnet build
=> [publish] RUN dotnet publish
=> [final] COPY --from=publish /app/publish .
=> exporting to image
Image built successfully: myfirstcontainerapp:v1
Verify the image was created:
docker images | grep myfirstcontainerapp
You should see:
myfirstcontainerapp v1 abc123def456 1 minute ago 216MB
Step 8: Run Your Container
Start a container from your image:
docker run -d -p 8080:8080 --name myapp myfirstcontainerapp:v1
Explanation:
-d- Run in detached mode (background)-p 8080:8080- Map host port 8080 to container port 8080--name myapp- Give the container a friendly namemyfirstcontainerapp:v1- The image to run
Verify the container is running:
docker ps
You should see:
CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
abc123def456 myfirstcontainerapp:v1 "dotnet MyFirstConta…" Up 10 seconds 0.0.0.0:8080->8080/tcp myapp
Step 9: Test Your Containerized Application
Test the API endpoint:
curl http://localhost:8080/weatherforecast
You should receive the JSON weather data, just like before, but now it's running in a container!
Step 10: View Container Logs
Check the application logs:
docker logs myapp
You should see application startup logs:
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://[::]:8080
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
Step 11: Stop and Remove the Container
When you're done testing:
# Stop the container
docker stop myapp
# Remove the container
docker rm myapp
# (Optional) Remove the image
docker rmi myfirstcontainerapp:v1
Complete Workflow Summary
Here's the complete workflow we just followed:
# 1. Create project
dotnet new webapi -n MyFirstContainerApp
cd MyFirstContainerApp
# 2. Add JD.MSBuild.Containers
dotnet add package JD.MSBuild.Containers
# 3. Configure Docker in .csproj
# (Edit file to add Docker properties)
# 4. Generate Dockerfile
dotnet build
# 5. Build Docker image
dotnet publish
# 6. Run container
docker run -d -p 8080:8080 --name myapp myfirstcontainerapp:v1
# 7. Test
curl http://localhost:8080/weatherforecast
# 8. Clean up
docker stop myapp
docker rm myapp
Troubleshooting
Issue: Dockerfile Not Generated
Symptom: Running dotnet build doesn't create a Dockerfile.
Solution: Verify Docker is enabled in your .csproj:
<DockerEnabled>true</DockerEnabled>
Issue: Docker Build Fails
Symptom: dotnet publish fails with Docker errors.
Solution:
- Verify Docker is running:
docker ps - Check Docker daemon is accessible:
docker info - Try building the Dockerfile manually:
docker build -t test .
Issue: Port Already in Use
Symptom: Container fails to start with "port already allocated" error.
Solution: Use a different port:
docker run -d -p 8081:8080 --name myapp myfirstcontainerapp:v1
curl http://localhost:8081/weatherforecast
Issue: Container Exits Immediately
Symptom: Container shows "Exited (0)" status immediately after starting.
Solution: Check logs:
docker logs myapp
Common causes:
- Application configuration errors
- Missing environment variables
- Port binding issues
What You've Learned
Congratulations! You've successfully:
✅ Created a .NET Web API project
✅ Added JD.MSBuild.Containers to your project
✅ Generated a Dockerfile automatically
✅ Built a Docker image from your application
✅ Ran your application in a container
✅ Tested the containerized application
Next Steps
Now that you understand the basics, explore:
- Advanced Tutorial - Custom configurations and complex scenarios
- Workflows - CI/CD integration patterns
- Best Practices - Production-ready configurations
- Samples - Real-world examples
Practice Exercise
Try these on your own:
- Change the image tag to
devinstead ofv1 - Add a custom health check endpoint and test it in the container
- Create a second endpoint and verify it works in the container
- Run multiple containers with different port mappings