
A comprehensive guide to containerizing ASP.NET Core applications, managing them with Docker Compose for development, and orchestrating production deployments with Kubernetes.
Your Name
Building scalable backend applications requires more than just solid code—it demands a robust deployment strategy. In my journey as a senior C# developer transitioning into DevOps, I've learned that containerization and orchestration are essential skills for modern backend engineers. This article walks through my practical approach to scaling ASP.NET Core applications from local development to production-grade Kubernetes clusters.
Before diving into code, let me clarify why containers matter for backend developers. Traditional deployment approaches often suffer from the classic "it works on my machine" problem. Docker solves this by packaging your entire application environment—code, dependencies, runtime—into a reproducible unit called a container.
For one of my recent projects, I had a Web API running perfectly on my Windows machine using Visual Studio and LocalDB, but deployment to a Linux server created unexpected issues with database connectivity, environment variables, and dependency versions. This is where containers became invaluable.
The foundation of containerization is the Dockerfile—a blueprint that defines how your application image is built. Here's my approach for a production-grade ASP.NET Core API:
# Multi-stage build - reduces final image size significantly
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS builder
WORKDIR /src
# Copy project files
COPY ["MyApi.csproj", "./"]
RUN dotnet restore "MyApi.csproj"
# Copy source and build
COPY . .
RUN dotnet build "MyApi.csproj" -c Release -o /app/build
# Publish stage
FROM builder AS publish
RUN dotnet publish "MyApi.csproj" -c Release -o /app/publish /p:UseAppHost=false
# Runtime stage - minimal image
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=publish /app/publish .
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENTRYPOINT ["dotnet", "MyApi.dll"]
Why this structure matters: Multi-stage builds reduce your final image size by ~80%. The builder stage includes the entire SDK, but the final runtime stage only includes the ASP.NET Core runtime—much smaller and more secure.
When building production Docker images for C# applications, I always follow these practices:
Use specific SDK versions: Never use latest tags. Pinning to 8.0 ensures reproducible builds across environments.
Leverage .NET's UseAppHost=false: This creates platform-independent binaries that work reliably in containers.
Set proper environment variables: The ASPNETCORE_URLS variable ensures your application listens on the correct port inside the container.
Use non-root users: For production, I always add a non-root user to reduce security risks:
RUN useradd -m -u 1000 appuser && chown -R appuser /app
USER appuser
Docker Compose transforms how I develop applications. Instead of running databases locally and managing multiple services manually, I define everything in a single file.
Here's a realistic setup I use for ASP.NET Core projects with multiple services:
version: '3.8'
services:
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionStrings__DefaultConnection=Server=sqlserver;Database=MyAppDb;User Id=sa;Password=YourPassword123!;Encrypt=false;
depends_on:
- sqlserver
volumes:
- .:/src
networks:
- myapp-network
sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
ACCEPT_EULA: Y
SA_PASSWORD: YourPassword123!
ports:
- "1433:1433"
volumes:
- sqlserver-data:/var/opt/mssql
networks:
- myapp-network
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- myapp-network
volumes:
sqlserver-data:
redis-data:
networks:
myapp-network:
driver: bridge
After mastering Docker Compose for local development, scaling to Kubernetes might seem overwhelming. But understanding the core concepts makes it manageable.
Here's my ASP.NET Core deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapi-deployment
spec:
replicas: 3
selector:
matchLabels:
app: myapi
template:
metadata:
labels:
app: myapi
spec:
containers:
- name: myapi
image: myregistry.azurecr.io/myapi:v1.0.0
ports:
- containerPort: 8080
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
The patterns I've shared represent thousands of deployments and the lessons learned from production failures. Whether you're just starting with containerization or optimizing existing pipelines, these principles remain constant: containerize properly, test thoroughly, and make scaling easy.
Scaling ASP.NET Core Applications with Docker and Kubernetes: A Practical Guide
A comprehensive guide to containerizing ASP.NET Core applications, managing them with Docker Compose for development, and orchestrating production deployments with Kubernetes.
Building Resilient APIs: Advanced ASP.NET Core Patterns for Production
Deep dive into production-grade API development patterns including dependency injection, middleware architecture, error handling, and implementing robust health checks.