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 TD
    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}"
    
    Command Why it is used
    az containerapp revision list ... Lists revisions so rollout state, traffic, and health can be verified.
    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"
    
    Command Why it is used
    az containerapp ingress show ... Reads ingress configuration such as exposure, target port, transport, and affinity.
    Expected output
    {
      "allowInsecure": false,
      "external": true,
      "fqdn": "<your-app-name>.<hash>.<region>.azurecontainerapps.io",
      "targetPort": 8000,
      "transport": "Auto",
      "traffic": [
        { "latestRevision": true, "weight": 100 }
      ]
    }
    

Verify in Azure Portal

ca-sample-d38538|Activity log|Activity|Edit columns|Refresh|Export Activity Logs|Download as CSV|Insights|Pin current filters|Reset filters|Management Group|None|Subscription|Visual Studio Enterprise Subscription|Timespan|Last 6 hours|Resource group|rg-aca-basics-d38538|Resource|ca-sample-d38538|Operation name|Status|Time|Time stamp|Subscription|Event initiated by|Auth Token for Container App Dev APIs|Succeeded|user@example.com

[Observed] ca-sample-d38538. Container App. Activity log. Activity. Edit columns. Refresh. Export Activity Logs. Download as CSV. Insights. Feedback. Pin current filters. Reset filters. Looking for Log Analytics?. Visit Log Analytics. Search. Quick Insights. Management Group. None. Subscription. Visual Studio Enterprise Subscription. All. Timespan. Last 6 hours. Resource group. rg-aca-basics-d38538. Resource. ca-sample-d38538. Add Filter. 1 item.. Operation name. Status. Time. Time stamp. Subscription. Event initiated by. Auth Token for Container App Dev APIs. Succeeded. 23 minutes .... Thu Jun 04 .... user@example.com. Overview. Activity log. Access control (IAM). Tags. Diagnose and solve problems. Resource visualizer. Application. Revisions and replicas. Containers. Scale. Volumes. Settings. Networking. Ingress. Custom domains. CORS. Security. Monitoring. Log stream. Logs. Console. Alerts. Metrics. Dashboards with Grafana. Advisor recommendations.

[Inferred] The resource-scoped filter chips Resource group and Resource appear to map to the same ${{ vars.RESOURCE_GROUP }} and ${{ vars.APP_NAME }} values passed to resourceGroup and containerAppName in Step-by-step Step 2. The Activity log blade appears consistent with the same container app remaining the deployment target when Step-by-step Step 3 runs azure/arm-deploy@v2 with resourceGroupName: ${{ vars.RESOURCE_GROUP }} and template: ./infra/main.bicep. The single Succeeded status appears consistent with the healthy rollout checks in Step-by-step Step 4, where az containerapp revision list is expected to show healthState: "Healthy" and runningState: "Running". The recent Time and Time stamp columns appear to map to the post-commit verification window described in Step-by-step Step 4, which tells you to trigger the workflow from a commit to main, confirm a new revision was created, and confirm traffic moved to the healthy revision.

[Not Proven] Additional GitHub Actions workflow detail, deployment template detail, and CLI command output are not visible on this view.

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