
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
To successfully Dockerize a ASP.NET Core Application, you must move beyond basic tutorials and implement multi-stage builds that separate SDK dependencies from runtime artifacts. This approach reduces image size by over 80% and minimizes the attack surface, which is critical for compliance frameworks like SOC 2 and ISO 27001. In this guide, I will walk you through the exact Dockerfile patterns, security configurations, and optimization strategies I use in production environments to ship secure, efficient .NET containers.
How do you write a production-grade Dockerfile to Dockerize a ASP.NET Core Application?
The foundation of any containerized .NET workload is the Dockerfile. When you Dockerize a ASP.NET Core Application for production, never use a single-stage build. Single-stage images include the full SDK (compilers, headers, debug tools), resulting in 900MB+ images with unnecessary vulnerabilities. Instead, adopt the multi-stage pattern shown below, which I have refined across dozens of enterprise deployments.
Optimized Multi-Stage Dockerfile
# Build stage
FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build
WORKDIR /src
# Copy csproj and restore first for layer caching
COPY ["MyApp.csproj", "."]
RUN dotnet restore "MyApp.csproj"
# Copy everything else and publish
COPY . .
RUN dotnet publish "MyApp.csproj" -c Release -o /app/publish \
--no-restore \
--self-contained false \
/p:PublishTrimmed=false
# Runtime stage
FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS runtime
WORKDIR /app
# Security: Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Set environment variables for production
ENV ASPNETCORE_URLS=http://+:8080
ENV DOTNET_ENVIRONMENT=Production
COPY --from=build /app/publish .
USER appuser
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApp.dll"] This Dockerfile follows several critical principles. First, we copy the .csproj file and run dotnet restore before copying source code. This leverages Docker’s layer caching: unless your dependencies change, subsequent builds skip the restore step entirely, cutting CI time significantly. Second, we use Alpine-based images to minimize footprint. Third, we explicitly create a non-root user—a mandatory requirement for passing container security scans in regulated environments. For teams managing complex infrastructure, understanding these layering principles is as important as Docker networking and volumes explained in our companion guide.
Why should you use multi-stage builds when you Dockerize a ASP.NET Core Application?
Multi-stage builds are not optional for production .NET workloads. They directly impact cost, security posture, and deployment velocity. When you Dockerize a ASP.NET Core Application without multi-stage separation, you ship build tools to production. Those tools contain known CVEs, increase startup latency, and consume more registry storage and network bandwidth during pulls.
| Criteria | Single-Stage Build | Multi-Stage Build |
|---|---|---|
| Final Image Size | ~900 MB | ~85 MB |
| CVE Attack Surface | High (includes SDK, compilers) | Low (runtime only) |
| Cold Start Time | Slower (larger filesystem) | Faster |
| Registry Storage Cost | 10x higher | Minimal |
| Compliance Readiness | Fails most security scans | Passes CIS benchmarks |
| Build Cache Efficiency | Poor (source changes invalidate restore) | Excellent (restore cached separately) |
In my experience helping Nepal-based fintech companies achieve SOC 2 compliance, the multi-stage build is often the first remediation item auditors flag. Shipping an SDK image to production violates the principle of least functionality. The runtime-only image contains only what is necessary to execute your compiled assembly, nothing more. If you are also evaluating orchestration options after containerization, our Kubernetes basics guide covers deploying these optimized images to clusters.
How do you secure a container when you Dockerize a ASP.NET Core Application?
Security cannot be an afterthought. When you Dockerize a ASP.NET Core Application, you must assume the container will be scanned by Trivy, Grype, or a cloud provider’s native scanner before it ever reaches production. Here are the non-negotiable hardening steps I apply to every .NET container:
- Run as non-root: Never run your application as UID 0. Create a dedicated user with no shell access and no home directory permissions beyond /app.
- Use read-only root filesystem: Set
readOnlyRootFilesystem: truein Kubernetes or--read-onlyin Docker run. Your app should write only to explicitly mounted tmpfs or volume paths. - Drop all capabilities: Use
--cap-drop=ALLand add back only what is absolutely required (rarely anything for a web API). - Pin base image digests: Tags like
9.0-alpineare mutable. Pin to SHA256 digests in production Dockerfiles to prevent supply chain attacks. - Exclude debug symbols: Ensure
<DebugType>None</DebugType>in your Release configuration to avoid shipping PDB files. - Scan before push: Integrate container image scanning with Trivy into your CI pipeline as a blocking gate.
For teams handling sensitive data, especially in Nepal’s growing fintech sector where data residency and protection regulations are tightening, these controls form the baseline for audit readiness. I have seen audits fail solely because containers ran as root or included debug artifacts. Do not let that be your team.
What are common mistakes when teams Dockerize a ASP.NET Core Application?
After reviewing hundreds of Dockerfiles across client engagements, certain anti-patterns appear repeatedly. Avoid these when you Dockerize a ASP.NET Core Application:
- Using Windows Server Core images in Linux clusters: Unless you have a hard Windows dependency, always target Linux. Windows containers are larger, slower to pull, and incompatible with most managed Kubernetes services.
- Hardcoding connection strings: Never embed secrets in the Dockerfile or baked-in config files. Use environment variables, mounted secret files, or a secrets manager. Our Kubernetes secrets management guide covers safe injection patterns.
- Ignoring health checks: Define
HEALTHCHECKin the Dockerfile or configure liveness/readiness probes in Kubernetes. Without them, orchestrators cannot detect hung processes. - Not setting ASPNETCORE_URLS: The default port in .NET 8+ is 8080, not 80. Explicitly set this environment variable to avoid binding failures in restricted container environments.
- Shipping self-contained deployments unnecessarily: Self-contained apps bundle the runtime, increasing image size by ~60MB. Use framework-dependent deployments when your base image already includes the matching runtime.
Ship Your Containerized .NET App With Confidence
When you Dockerize a ASP.NET Core Application correctly, you gain reproducible builds, smaller attack surfaces, faster deployments, and audit-ready artifacts. The multi-stage Dockerfile, non-root execution, and layer caching patterns described here are battle-tested across production systems serving millions of requests. Do not settle for tutorial-grade containers in professional environments. If your team needs help implementing these patterns, hardening existing containers, or preparing for a compliance audit, reach out to discuss your infrastructure. I help teams build systems that are secure, observable, and ready for scrutiny from day one.