Container deployment has become the cornerstone of scalable, repeatable application delivery. .NET 10 represents the latest evolution of Microsoft's cloud-native framework, offering exceptional performance, deep cross-platform support, and tight integration with modern DevOps practices. Developing with .NET 10 offers incredible performance and cross-platform capability. When paired with Docker, .NET 10 applications become truly portable artifacts that run identically across development laptops, CI/CD pipelines, staging environments, and production infrastructure—whether on-premises, cloud-hosted, or hybrid. This comprehensive guide walks you through a professional-grade containerization workflow using the .NET CLI and Docker's automated tooling, taking you from a fresh project scaffold to a production-ready, optimized container image. The next logical step is to deploy that application using Docker, which ensures that your code runs identically everywhere—from your local machine to any cloud environment. This guide outlines the most efficient process for containerizing any new .NET 10 web application using the integrated docker init tool.
Why Docker and .NET 10 Are the Perfect Match
The promise of containerization is straightforward in theory but demanding in practice: write once, deploy everywhere. .NET 10 and Docker together fulfill this promise with remarkable elegance.
Reproducibility is the first pillar. Every developer, CI agent, and production server running your Docker image is executing identical bytecode in an identical runtime environment. No more "works on my machine" frustrations. Configuration drift—where servers gradually diverge due to manual patches, version mismatches, or environment-specific tweaks—becomes moot when your entire runtime is packaged as code.
Portability extends beyond reproducibility. A .NET 10 Docker image can run anywhere Docker is supported: Linux and Windows containers, on-premises data centers, every major cloud provider (AWS ECS, Azure Container Instances, Google Cloud Run), Kubernetes clusters, edge devices, or developer workstations. Your investment in containerization unlocks unprecedented deployment flexibility. You're no longer locked into a single platform or hosting provider.
Performance is where .NET 10 shines. The latest framework includes performance improvements across the runtime, IL compiler, and garbage collector. Combining this with Docker's efficient resource isolation means your containerized .NET 10 applications run lean and fast, scaling efficiently under load.
Security and isolation are architectural benefits of containerization. Your application runs in a lightweight, isolated sandbox. Changes to one container don't cascade to others. Updates to your base image can be published centrally and adopted across your entire fleet without rewriting application code. This decoupling of application and infrastructure is essential for modern security practices.
From a team perspective, Docker provides a shared contract between developers and Operations team. Developers focus on code and dependencies within the Dockerfile; infrastructure teams focus on orchestration, networking, and resource allocation at the container level. This separation of concerns accelerates both development velocity and operational reliability.
PreRequisites: Setting up your development Environment
1. Install .NET 10 SDK
Download and install the .NET 10 SDK from dotnet.microsoft.com. Choose the installer for your operating system (Windows, macOS, or Linux).
Verify installation:
dotnet --version
dotnet --list-sdks
You should see version 10.0.x listed.
2. Install Docker Desktop
Download Docker Desktop from docker.com and run the installer for your operating system. Start Docker Desktop after installation.
Verify installation:
docker --version
Create a new Web application
Using CLI, create a new webapplication and make sure to set the target framework to dotnet 10.0
dotnet new webapp -f net10.0 -o webapplication1
The -f net10.0 flag explicitly targets to create the project with .Net 10.0 as target framework as shown in below figure.
![webapppic]()
![pic2]()
Once scaffolded, your project contains:
Program.cs: The entry point, where you configure services and middleware
WebApplication1.csproj: The project file defining dependencies and build configuration
Properties/launchSettings.json: Development launch profiles, including port mappings and logging
Standard folders like Pages, wwwroot, and others depending on your template choice
Build and Test Your Application Locally
Before moving to containers, verify the application runs correctly on your host:
dotnet run
![pic3]()
The CLI compiles your project, restores NuGet packages (if necessary), and starts the Kestrel web server. You should see output similar to below
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5172
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
Open a browser and navigate to the HTTPS URL (in this example, https://localhost:5172). You should see the default template page. If you're using a self-signed development certificate, your browser will warn you about the certificate; this is expected and safe to bypass during local development.
This smoke test confirms that your application compiles, the Kestrel server starts correctly, and the basic request/response cycle works. Any configuration issues, missing dependencies, or logic errors will surface immediately. Catching these now saves time later in the Docker build pipeline.
Containerizing with Docker Init
Docker's init command is a game-changer for .NET developers. It analyzes your project structure and generates a production-grade Docker configuration tailored to your tech stack, eliminating tedious manual Dockerfile authoring for the common case.
Make sure you complete the pre-requisities above and ensure Docker Desktop is running, from your project root folder, run the below command
docker init
![pic4]()
The command prompts you with a series of questions:
Application platform: Select .NET (or .NET ASP.NET Core if more specific)
Version: It will auto-detect .NET 10 from your project file
Port: Enter the port your application should listen on (default is often 8080)
After responding to the prompts, Docker Init generates three critical files as shown in the below figure.
![pic5]()
Dockerfile
The Dockerfile is the recipe for building your container image. For .NET 10, Docker Init typically generates a multi-stage build file as shown below.
# syntax=docker/dockerfile:1
# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/go/dockerfile-reference/
# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7
################################################################################
# Learn about building .NET container images:
# https://github.com/dotnet/dotnet-docker/blob/main/samples/README.md
# Create a stage for building the application.
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
COPY . /source
WORKDIR /source
# This is the architecture you're building for, which is passed in by the builder.
# Placing it here allows the previous steps to be cached across architectures.
ARG TARGETARCH
# Build the application.
# Leverage a cache mount to /root/.nuget/packages so that subsequent builds don't have to re-download packages.
# If TARGETARCH is "amd64", replace it with "x64" - "x64" is .NET's canonical name for this and "amd64" doesn't
# work in .NET 6.0.
RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages \
dotnet publish -a ${TARGETARCH/amd64/x64} --use-current-runtime --self-contained false -o /app
# If you need to enable globalization and time zones:
# https://github.com/dotnet/dotnet-docker/blob/main/samples/enable-globalization.md
################################################################################
# Create a new stage for running the application that contains the minimal
# runtime dependencies for the application. This often uses a different base
# image from the build stage where the necessary files are copied from the build
# stage.
#
# The example below uses an aspnet alpine image as the foundation for running the app.
# It will also use whatever happens to be the most recent version of that tag when you
# build your Dockerfile. If reproducibility is important, consider using a more specific
# version (e.g., aspnet:7.0.10-alpine-3.18),
# or SHA (e.g., mcr.microsoft.com/dotnet/aspnet@sha256:f3d99f54d504a21d38e4cc2f13ff47d67235efeeb85c109d3d1ff1808b38d034).
FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final
WORKDIR /app
# Copy everything needed to run the app from the "build" stage.
COPY --from=build /app .
# Switch to a non-privileged user (defined in the base image) that the app will run under.
# See https://docs.docker.com/go/dockerfile-user-best-practices/
# and https://github.com/dotnet/dotnet-docker/discussions/4764
USER $APP_UID
ENTRYPOINT ["dotnet", "WebApplication1.dll"]
Multi-stage builds are the cornerstone of this Dockerfile. They solve a critical problem: if you built your image using only the SDK stage, the final image would be over 2 GB, containing the entire .NET SDK, build tools, source code, and intermediate artifacts. None of these are needed at runtime; they're build-time concerns only.
The multi-stage approach separates concerns:
Stage 1 (build): Starts from the full .NET SDK (mcr.microsoft.com/dotnet/sdk:10.0), which includes compilers, build tools, and everything needed to compile C#.
Stage 2 (publish): Runs dotnet publish, which compiles the application in Release mode and packages only the runtime-necessary binaries into an /app/publish folder. Source code is not included.
Stage 3 (runtime): Starts from a lean ASP.NET Core runtime image (mcr.microsoft.com/dotnet/aspnet:10.0), which contains only the .NET runtime, without the SDK or build tools. The COPY --from=publish instruction brings only the published binaries from Stage 2.
The result: a final image of roughly 150–300 MB (depending on your application), down from over 2 GB—an 80%+ reduction. This has cascading benefits: faster builds, quicker deployments, lower storage and bandwidth costs, and a smaller attack surface for security.
Layer caching is another critical optimization baked into this structure. Docker caches each layer (each line in the Dockerfile). When you change your C# code, Docker rebuilds only the layers after the change, reusing earlier cached layers. By copying *.csproj and running dotnet restore early, you maximize cache hits. If only your code changes (not your dependencies), the restore layer is skipped, and the build is much faster.
.dockerignore
This file tells docker which files to exclude when building the image context. Excluding bin and obj folders are important as these folders contain compiled binaries from your host machine and they are not needed within Docker context. The build would happen inside the container to generate new binaries. Similarly all the irrelevant files/folders are not needed and these are added as part of .dockerignore file.
**/.git
**/.gitignore
**/.vs
**/.vscode
**/bin
**/obj
**/node_modules
......
Compose.yaml
This file orchestrates the local containerized development and is shown below
services:
server:
build:
context: .
target: final
ports:
- 8080:8080
Visual studio code and visual studio is smart enough to provide easy way to run these services by automatically creating "Run all Services" button.
![pic6]()
Lets look at each section
services: Defines services in your stack.
build: Specifies how to build the image. context: . means "use the current directory as the build context."
ports: Maps container ports to host ports. "8080:8080" means "forward host port 8080 to container port 8080." When you access localhost:8080 on your development machine, traffic is routed to port 8080 inside the container.
Compose.yaml is your main starting point to run your application inside docker as container. Depending on your application, you can do adjustments to the compose.yaml file and there are clear comments provided in the auto generated file to give you more knowledge about how to add other services like adding PostgreSQL or any other dependencies that your application can use.
Readme.Docker.md
This files provided detailed instructions on how to build and run your application as shown below.
![pic7]()
Let's use these instructions to build and run your application. It also provides instructions on how to deploy your application to the cloud.
Building and running your application as a container inside Docker
Once you have adjusted the configuration as per your project needs, you can build and run your application by running below docker command from the terminal
docker compose up --build
"docker compose up" command starts all services defined in your compose.yaml. Instead of executing command, you can also click on "run all services" button within your Visual studio editor.
depending on the size of your base images within your docker file, it takes few minutes to build and run your application as shown in below image.
![pic8]()
Once its completed, you can navigate to your application by opening the url https://localhost:8080 within your browser and you can also verify the docker image created by navigating to Images tab within Docker Desktop as shown below.
![pic9]()
You can also view application logs directly within your Docker Desktop
![pic10]()
Before pushing this to docker hub repository or running in production, scan for any vulnerabilities by using Docker Scout command which is built into the Docker CLI.
Conclusion
Containerizing .NET 10 applications with Docker transforms development workflow and deployment reliability. The docker init tool streamlines the process, generating multi-stage Dockerfiles that produce lean, efficient images. Combined with Docker Compose for local development or managed container services for production, this workflow delivers reproducibility, portability, and operational excellence.
From local development to global deployment, your .NET 10 application now runs consistently, scales elastically, and integrates seamlessly with modern cloud-native infrastructure. The investment in containerization pays dividends in deployment velocity, infrastructure cost, and team productivity.
This post is part of C# Advent organized by @mgroves.