Table of Contents
Why Docker + CI/CD?
The classic "works on my machine" problem is real. Docker solves it by packaging your application with all its dependencies into a container. Combined with GitHub Actions for CI/CD, you get:
- Consistent environments: Dev, staging, and production run the exact same container - Automated testing: Run tests on every push before deploying - Zero-downtime deployments: Azure swaps containers seamlessly - Rollback capability: Previous container images are always available in the registry
Dockerizing the .NET Application
A multi-stage Dockerfile keeps the final image small by separating the build environment from the runtime environment. The build stage uses the full .NET SDK, while the runtime stage uses the minimal ASP.NET runtime image.
# Dockerfile — Multi-stage build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY *.csproj ./
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENTRYPOINT ["dotnet", "MyApp.dll"]Multi-stage builds reduce image size by 60-80%. The SDK image is ~700MB, but the runtime image is only ~200MB.
Azure Container Registry Setup
Azure Container Registry (ACR) stores your Docker images privately. Create one in the Azure portal or via CLI:
1. Create an ACR instance in your resource group 2. Enable Admin user for GitHub Actions authentication 3. Note the login server URL (yourname.azurecr.io) 4. Store credentials as GitHub repository secrets
ACR integrates natively with Azure Web App, so deployments pull images directly without external registries.
GitHub Actions Workflow
The CI/CD pipeline builds the Docker image, pushes it to ACR, and deploys to Azure Web App — all triggered on push to main.
# .github/workflows/deploy.yml
name: Build and Deploy to Azure
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to ACR
uses: azure/docker-login@v1
with:
login-server: ${{ secrets.ACR_LOGIN_SERVER }}
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}
- name: Build and push image
run: |
docker build -t ${{ secrets.ACR_LOGIN_SERVER }}/myapp:${{ github.sha }} .
docker push ${{ secrets.ACR_LOGIN_SERVER }}/myapp:${{ github.sha }}
- name: Deploy to Azure Web App
uses: azure/webapps-deploy@v2
with:
app-name: "my-web-app"
images: "${{ secrets.ACR_LOGIN_SERVER }}/myapp:${{ github.sha }}"Azure Web App Configuration
Azure Web App for Containers needs a few settings:
- Container Settings: Point to your ACR image with the :latest or specific tag - Continuous Deployment: Enable webhook so Azure pulls new images automatically - Health Check: Configure a health check endpoint (/health) for zero-downtime deploys - App Settings: Set environment variables (connection strings, API keys) in Azure portal
Azure Web App automatically handles SSL, custom domains, scaling, and load balancing. Combined with Docker, you get enterprise-grade deployments with minimal configuration.
Always tag images with the git SHA, not just :latest. This makes rollbacks trivial — just redeploy the previous SHA.
Key Takeaways
Multi-stage Docker builds keep production images small and secure
GitHub Actions automates the entire build → push → deploy pipeline
Azure Container Registry provides private, integrated image storage
Tag images with git SHA for instant rollback capability
Azure Web App handles SSL, scaling, and load balancing automatically
Written by Surya Kanagaraj
Senior Fullstack Developer & AWS Cloud Engineer. Building production serverless apps on AWS. Available for freelancing.