Skip to content

06 - CI/CD with GitHub Actions

Automate build and deployment so every commit can produce a new Container App revision. This tutorial uses GitHub Actions, ACR, and Azure Container Apps deploy actions.

Infrastructure Context

Service: Container Apps (Consumption) | Network: VNet integrated | VNet: ✅

This tutorial assumes a production-ready Container Apps deployment with a custom VNet, ACR with managed identity pull, and private endpoints for backend services.

flowchart TD
    INET[Internet] -->|HTTPS| CA["Container App\nConsumption\nLinux Python 3.11"]

    subgraph VNET["VNet 10.0.0.0/16"]
        subgraph ENV_SUB["Environment Subnet 10.0.0.0/23\nDelegation: Microsoft.App/environments"]
            CAE[Container Apps Environment]
            CA
        end
        subgraph PE_SUB["Private Endpoint Subnet 10.0.2.0/24"]
            PE_ACR[PE: ACR]
            PE_KV[PE: Key Vault]
            PE_ST[PE: Storage]
        end
    end

    PE_ACR --> ACR[Azure Container Registry]
    PE_KV --> KV[Key Vault]
    PE_ST --> ST[Storage Account]

    subgraph DNS[Private DNS Zones]
        DNS_ACR[privatelink.azurecr.io]
        DNS_KV[privatelink.vaultcore.azure.net]
        DNS_ST[privatelink.blob.core.windows.net]
    end

    PE_ACR -.-> DNS_ACR
    PE_KV -.-> DNS_KV
    PE_ST -.-> DNS_ST

    CA -.->|System-Assigned MI| ENTRA[Microsoft Entra ID]
    CAE --> LOG[Log Analytics]
    CA --> AI[Application Insights]

    style CA fill:#107c10,color:#fff
    style VNET fill:#E8F5E9,stroke:#4CAF50
    style DNS fill:#E3F2FD

CI/CD Pipeline Flow

graph LR
    PUSH[Push to main] --> GHA[GitHub Actions]
    GHA --> ACR[ACR Build]
    ACR --> DEPLOY[Deploy Revision]
    DEPLOY --> HEALTH[Health Check]

Prerequisites

Never expose credentials in workflow logs

Keep all credential material in GitHub Secrets and avoid printing secret-derived values in shell steps. Use masked placeholders in documentation and workflow examples.

Step-by-step

  1. Configure repository variables and secrets

  2. Variables: RESOURCE_GROUP, APP_NAME, ACR_NAME

  3. Secrets: AZURE_CREDENTIALS, REGISTRY_USERNAME, REGISTRY_PASSWORD

Example AZURE_CREDENTIALS JSON (masked):

{
  "clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "clientSecret": "<client-secret>",
  "subscriptionId": "<subscription-id>",
  "tenantId": "<tenant-id>"
}
  1. Create workflow file
name: Deploy to Azure Container Apps

on:
  push:
    branches: [ main ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Azure Login
        uses: azure/login@v2
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: ACR Login
        uses: azure/docker-login@v2
        with:
          login-server: ${{ vars.ACR_NAME }}.azurecr.io
          username: ${{ secrets.REGISTRY_USERNAME }}
          password: ${{ secrets.REGISTRY_PASSWORD }}

      - name: Build and push image
        run: |
          docker build --tag ${{ vars.ACR_NAME }}.azurecr.io/${{ vars.APP_NAME }}:${{ github.sha }} .
          docker push ${{ vars.ACR_NAME }}.azurecr.io/${{ vars.APP_NAME }}:${{ github.sha }}

      - name: Deploy Container App
        uses: azure/container-apps-deploy-action@v1
        with:
          imageToDeploy: ${{ vars.ACR_NAME }}.azurecr.io/${{ vars.APP_NAME }}:${{ github.sha }}
          resourceGroup: ${{ vars.RESOURCE_GROUP }}
          containerAppName: ${{ vars.APP_NAME }}
  1. Add infrastructure deployment (optional but recommended)
      - name: Deploy Infrastructure
        uses: azure/arm-deploy@v2
        with:
          resourceGroupName: ${{ vars.RESOURCE_GROUP }}
          template: ./infra/main.bicep
          parameters: appName=${{ vars.APP_NAME }} acrName=${{ vars.ACR_NAME }}
  1. Validate rollout behavior

    • Trigger workflow from a commit to main.
    • Confirm a new revision was created.
    • Confirm traffic moved to healthy revision.
    az containerapp revision list \
      --name "$APP_NAME" \
      --resource-group "$RG" \
      --query "[].{name:name,active:properties.active,trafficWeight:properties.trafficWeight,replicas:properties.replicas,healthState:properties.healthState,runningState:properties.runningState}"
    
    Expected output
    [
      {
        "name": "<your-app-name>--xxxxxxx",
        "active": true,
        "trafficWeight": 100,
        "replicas": 1,
        "healthState": "Healthy",
        "runningState": "Running"
      }
    ]
    
    az containerapp ingress show \
      --name "$APP_NAME" \
      --resource-group "$RG"
    
    Expected output
    {
      "allowInsecure": false,
      "external": true,
      "fqdn": "<your-app-name>.<hash>.<region>.azurecontainerapps.io",
      "targetPort": 8000,
      "transport": "Auto",
      "traffic": [
        { "latestRevision": true, "weight": 100 }
      ]
    }
    

Advanced Topics

  • Add pre-deploy tests and security scanning gates.
  • Implement staged deployment with manual approval.
  • Use multiple active revisions for canary rollout in pipeline.

Use immutable image tags in pipelines

Prefer commit SHA or release-based tags for image versions. Immutable tags make revision-to-commit tracing straightforward during incident response.

See Also

Sources