Blog
Oct 27, 2025 - 12 MIN READ
Scaling ASP.NET Core Applications with Docker and Kubernetes: A Practical Guide

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.

Your Name

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.

Understanding the Container Journey

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.

The Problem We're Solving

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.

Phase 1: Containerizing Your ASP.NET Core Application

Creating Your First Dockerfile

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.

Key Dockerfile Principles

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

Phase 2: Local Development with Docker Compose

Docker Compose transforms how I develop applications. Instead of running databases locally and managing multiple services manually, I define everything in a single file.

My Docker Compose Setup

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

Phase 3: Building Your Kubernetes Strategy

After mastering Docker Compose for local development, scaling to Kubernetes might seem overwhelming. But understanding the core concepts makes it manageable.

Creating Your First Deployment

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"

Conclusion

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.

Built with Nuxt UI • © 2025 Behnam Nouri