Azure Container Apps: unique container image tags with GitHub Actions and Terraform using the latest tag trick

In the world of DevOps and containerization, choosing the right strategies for deploying and managing container images is crucial. One common best practice is to use unique tags for container images, typically tied to a specific commit SHA, rather than a stable tag like “latest.” This approach ensures traceability and helps in avoiding unintended changes in your deployment pipeline. However, when working with Azure Container Apps alongside tools like GitHub Actions and Terraform, this practice can introduce some challenges.

The Challenge

When you’re using Azure Container Apps for deploying containerized applications, it’s often recommended to use unique image tags generated from commit SHAs. This way, you can easily track which version of your application is running in your production environment, and it aligns perfectly with the principles of immutable infrastructure.

In GitHub Actions, implementing this strategy is straightforward. You can deploy your image with a tag like this:

- name: Deploy to Azure Container Apps
  run: |
    az containerapp update --resource-group myresourcegroup --name myapp --image myregistry.azurecr.io/app:${{ github.sha }}

This deploys the container image with a tag derived from the commit SHA, ensuring that each deployment is unique and identifiable.

However, integrating this approach with Terraform can be a bit tricky. Terraform requires you to specify static values for resources, and if you use commit SHA as the image tag within your Terraform configuration, it will continuously attempt to modify the resource due to the ever-changing SHA. This leads to unnecessary changes and can make your Terraform deployments less efficient.

The Solution

The solution to this problem lies in striking a balance between GitHub Actions and Terraform. While GitHub Actions can dynamically generate tags using commit SHAs, Terraform requires static values. To harmonize the two, consider the following approach:

1. Continue Using the “latest” Tag in Terraform

In your Terraform configuration for Azure Container Apps, continue to use a stable tag like “latest” for your container image. For example:

resource "azurerm_container_app" "example" {
  ...
  template {
    container {
      name   = "myapp"
      image  = "myregistry.azurecr.io/app:latest"

    // Other container settings
    }
  }
}

2. Use Digests for Image Deployment in GitHub Actions

In your GitHub Actions workflow, deploy the container image using the image digest instead of the tag. This ensures that you’re always deploying the exact same image, even if the tag changes. Here’s an example of how you can modify your GitHub Actions workflow:

name: build-deploy-container-app

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  build-and-deploy:
    name: "Build and deploy to Container App"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: "Login via Azure CLI"
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: "Build and push image to ACR"
        run: |
          az acr login --name myregistry
          docker build --file folder/Dockerfile . --tag myregistry.azurecr.io/app:latest
          docker push myregistry.azurecr.io/app:latest
          
      - name: "Deploy to Container App"
        run: |
          # Get imageDigest
          imageDigest=$(docker inspect --format='{{index .RepoDigests 0}}' myregistry.azurecr.io/worker:latest)
          echo "imageDigest is: << $imageDigest >>"
          
          # Set Container App Revision Suffix as "sha" + first 8 characters from digest i.e. "sha687166af"
          shaDigest=${imageDigest#myregistry.azurecr.io/worker@sha256:}
          suffix=sha$(echo $shaDigest | cut -c -8 )
          echo "suffix is: << $suffix>>"
          
          # Deploy to Container App
          az containerapp update --resource-group myresourcegroup --name mycontainerapp --image $imageDigest --revision-suffix $suffix

Benefits of this Approach

This approach offers several advantages:

  1. Consistency: By using image digests in GitHub Actions, you ensure that the same image is deployed consistently, even if the tag changes due to new commits.
  2. Efficiency: Terraform deployments remain efficient, as the static “latest” tag prevents unnecessary resource modifications.
  3. Traceability: You can still track which commit SHA corresponds to a deployed image in your Azure Container Registry.
  4. Flexibility: You have the flexibility to choose different tags for different purposes (e.g., “latest” for development and specific SHAs for production) while avoiding conflicts between GitHub Actions and Terraform.

In conclusion, combining the use of commit SHA-based tags with GitHub Actions and stable tags like “latest” in Terraform allows you to maintain best practices in both CI/CD and infrastructure-as-code. This approach strikes a balance between flexibility and stability, ensuring that your containerized applications are deployed efficiently and consistently in Azure Container Apps.