Deploy ASP.NET Core to production: Docker containerization, Kubernetes, Azure deployment, and CI/CD pipeline automation.
Deploying ASP.NET Core to production involves containerizing with Docker, orchestrating with Kubernetes, automating with CI/CD pipelines, and publishing to cloud platforms like Azure. A well-designed deployment pipeline ensures repeatable, zero-downtime releases.
Multi-stage Dockerfiles produce small, production-ready images:
1# Dockerfile
2FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
3WORKDIR /app
4EXPOSE 8080
5EXPOSE 8081
6
7FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
8WORKDIR /src
9COPY ["MyApi/MyApi.csproj", "MyApi/"]
10RUN dotnet restore "MyApi/MyApi.csproj"
11COPY . .
12WORKDIR "/src/MyApi"
13RUN dotnet build "MyApi.csproj" -c Release -o /app/build
14
15FROM build AS publish
16RUN dotnet publish "MyApi.csproj" -c Release -o /app/publish /p:UseAppHost=false
17
18FROM base AS final
19WORKDIR /app
20COPY --from=publish /app/publish .
21ENTRYPOINT ["dotnet", "MyApi.dll"]
22
23# .dockerignore
24**/bin/
25**/obj/
26**/.vs/
27**/.git/
28**/node_modules/
29**/*.user
30**/*.log
31
32# docker-compose.yml
33version: '3.8'
34
35services:
36 api:
37 build:
38 context: .
39 dockerfile: Dockerfile
40 ports:
41 - "5000:8080"
42 environment:
43 - ASPNETCORE_ENVIRONMENT=Production
44 - ConnectionStrings__Default=Host=db;Database=myapp;Username=postgres;Password=secret
45 depends_on:
46 - db
47 - redis
48
49 db:
50 image: postgres:16-alpine
51 environment:
52 POSTGRES_DB: myapp
53 POSTGRES_USER: postgres
54 POSTGRES_PASSWORD: secret
55 volumes:
56 - postgres_data:/var/lib/postgresql/data
57 ports:
58 - "5432:5432"
59
60 redis:
61 image: redis:7-alpine
62 ports:
63 - "6379:6379"
64 volumes:
65 - redis_data:/data
66
67volumes:
68 postgres_data:
69 redis_data:
70
71# Commands
72docker build -t myapi:latest .
73docker run -d -p 5000:8080 --name myapi myapi:latest
74docker-compose up -d
75docker-compose logs -f apiDeploy with Kubernetes Deployment, Service, and Ingress manifests:
1# deployment.yaml
2apiVersion: apps/v1
3kind: Deployment
4metadata:
5 name: myapi
6 labels:
7 app: myapi
8spec:
9 replicas: 3
10 selector:
11 matchLabels:
12 app: myapi
13 template:
14 metadata:
15 labels:
16 app: myapi
17 spec:
18 containers:
19 - name: myapi
20 image: myregistry.azurecr.io/myapi:latest
21 ports:
22 - containerPort: 8080
23 resources:
24 requests:
25 memory: "128Mi"
26 cpu: "100m"
27 limits:
28 memory: "256Mi"
29 cpu: "500m"
30 env:
31 - name: ASPNETCORE_ENVIRONMENT
32 value: "Production"
33 - name: ConnectionStrings__Default
34 valueFrom:
35 secretKeyRef:
36 name: myapi-secrets
37 key: db-connection
38 livenessProbe:
39 httpGet:
40 path: /health/live
41 port: 8080
42 initialDelaySeconds: 10
43 periodSeconds: 10
44 readinessProbe:
45 httpGet:
46 path: /health/ready
47 port: 8080
48 initialDelaySeconds: 5
49 periodSeconds: 5
50
51---
52# service.yaml
53apiVersion: v1
54kind: Service
55metadata:
56 name: myapi-service
57spec:
58 selector:
59 app: myapi
60 ports:
61 - protocol: TCP
62 port: 80
63 targetPort: 8080
64 type: ClusterIP
65
66---
67# ingress.yaml
68apiVersion: networking.k8s.io/v1
69kind: Ingress
70metadata:
71 name: myapi-ingress
72 annotations:
73 kubernetes.io/ingress.class: nginx
74 cert-manager.io/cluster-issuer: letsencrypt-prod
75spec:
76 tls:
77 - hosts:
78 - api.myapp.com
79 secretName: myapi-tls
80 rules:
81 - host: api.myapp.com
82 http:
83 paths:
84 - path: /
85 pathType: Prefix
86 backend:
87 service:
88 name: myapi-service
89 port:
90 number: 80
91
92---
93# secrets.yaml
94apiVersion: v1
95kind: Secret
96metadata:
97 name: myapi-secrets
98type: Opaque
99stringData:
100 db-connection: "Host=postgres;Database=myapp;..."
101
102# Commands
103kubectl apply -f deployment.yaml
104kubectl apply -f service.yaml
105kubectl apply -f ingress.yaml
106kubectl get pods
107kubectl logs -f deployment/myapiPublish to Azure using CLI or GitHub Actions:
1# Azure CLI commands
2
3# Login and set subscription
4az login
5az account set --subscription "My Subscription"
6
7# Create resource group
8az group create --name myapp-rg --location eastus
9
10# Create App Service Plan
11az appservice plan create \\
12 --name myapp-plan \\
13 --resource-group myapp-rg \\
14 --sku B1 \\
15 --is-linux
16
17# Create Web App
18az webapp create \\
19 --name myapi-app \\
20 --resource-group myapp-rg \\
21 --plan myapp-plan \\
22 --runtime "DOTNET|8.0"
23
24# Configure app settings
25az webapp config appsettings set \\
26 --name myapi-app \\
27 --resource-group myapp-rg \\
28 --settings \\
29 ASPNETCORE_ENVIRONMENT=Production \\
30 ConnectionStrings__Default="@Microsoft.KeyVault(VaultName=myapp-kv;SecretName=DbConnection)"
31
32# Deploy from local
33dotnet publish -c Release -o ./publish
34cd ./publish
35zip -r ../deploy.zip .
36az webapp deploy \\
37 --name myapi-app \\
38 --resource-group myapp-rg \\
39 --src-path ../deploy.zip
40
41# Or deploy from container registry
42az webapp config container set \\
43 --name myapi-app \\
44 --resource-group myapp-rg \\
45 --docker-custom-image-name myregistry.azurecr.io/myapi:latest \\
46 --docker-registry-server-url https://myregistry.azurecr.io
47
48# Enable managed identity
49az webapp identity assign \\
50 --name myapi-app \\
51 --resource-group myapp-rg
52
53# Create Azure Container Apps
54az containerapp create \\
55 --name myapi \\
56 --resource-group myapp-rg \\
57 --environment myapp-env \\
58 --image myregistry.azurecr.io/myapi:latest \\
59 --target-port 8080 \\
60 --ingress external \\
61 --min-replicas 1 \\
62 --max-replicas 10 \\
63 --cpu 0.5 \\
64 --memory 1.0Gi \\
65 --secrets db-connection="..." \\
66 --env-vars ConnectionStrings__Default=secretref:db-connectionAutomate test, build, push, and deploy on merge:
1# .github/workflows/deploy.yml
2name: Build and Deploy
3
4on:
5 push:
6 branches: [main]
7 pull_request:
8 branches: [main]
9
10env:
11 DOTNET_VERSION: '8.0.x'
12 REGISTRY: ghcr.io
13 IMAGE_NAME: \${{ github.repository }}
14
15jobs:
16 build:
17 runs-on: ubuntu-latest
18 steps:
19 - uses: actions/checkout@v4
20
21 - name: Setup .NET
22 uses: actions/setup-dotnet@v4
23 with:
24 dotnet-version: \${{ env.DOTNET_VERSION }}
25
26 - name: Restore dependencies
27 run: dotnet restore
28
29 - name: Build
30 run: dotnet build --no-restore -c Release
31
32 - name: Test
33 run: dotnet test --no-build -c Release --verbosity normal
34
35 - name: Publish
36 run: dotnet publish -c Release -o ./publish
37
38 - name: Upload artifact
39 uses: actions/upload-artifact@v4
40 with:
41 name: webapp
42 path: ./publish
43
44 docker:
45 needs: build
46 runs-on: ubuntu-latest
47 if: github.ref == 'refs/heads/main'
48 permissions:
49 contents: read
50 packages: write
51 steps:
52 - uses: actions/checkout@v4
53
54 - name: Log in to Container Registry
55 uses: docker/login-action@v3
56 with:
57 registry: \${{ env.REGISTRY }}
58 username: \${{ github.actor }}
59 password: \${{ secrets.GITHUB_TOKEN }}
60
61 - name: Build and push Docker image
62 uses: docker/build-push-action@v5
63 with:
64 context: .
65 push: true
66 tags: |
67 \${{ env.REGISTRY }}/\${{ env.IMAGE_NAME }}:latest
68 \${{ env.REGISTRY }}/\${{ env.IMAGE_NAME }}:\${{ github.sha }}
69
70 deploy:
71 needs: docker
72 runs-on: ubuntu-latest
73 if: github.ref == 'refs/heads/main'
74 environment: production
75 steps:
76 - name: Deploy to Azure Container Apps
77 uses: azure/container-apps-deploy-action@v1
78 with:
79 appSourcePath: \${{ github.workspace }}
80 acrName: myregistry
81 containerAppName: myapi
82 resourceGroup: myapp-rg
83 imageToDeploy: \${{ env.REGISTRY }}/\${{ env.IMAGE_NAME }}:\${{ github.sha }}Your progress is saved in this browser, on this device only.