DEV Community

Tuan Saad
Tuan Saad

Posted on

How to Configure Container App jobs as GitHub Action runners

Azure Container Apps GitHub Actions Runner Setup Guide

Overview

Tired of paying for CI/CD runners that sit idle? Azure Container Apps jobs automatically spin up GitHub runners only when your workflows run, then shut them down when finished. You only pay for actual usage while keeping pipelines secure in private networks—no more expensive always-on VMs eating your budget

This guide will help you set up a GitHub Actions self-hosted runner using Azure Container Apps. The runner will automatically scale based on your GitHub workflow queue and execute your CI/CD pipelines in isolated containers.

Prerequisites

Before starting, ensure you have:

  • Azure CLI installed and configured
  • An active Azure subscription
  • A GitHub repository with Actions enabled
  • GitHub Personal Access Token (PAT) with appropriate permissions

Required Permissions for GitHub PAT

Your GitHub Personal Access Token needs the following scopes:

  • repo (Full control of private repositories)
  • workflow (Update GitHub Action workflows)
  • admin:org (if using organization-level runners)

Step 1: Install Azure Container Apps Extension

First, add the Container Apps extension to Azure CLI:

az extension add --name containerapp --upgrade

Enter fullscreen mode Exit fullscreen mode

Step 2: Configure Environment Variables

Set up your configuration variables. Replace the placeholder values with your actual information:

# Resource configuration
RESOURCE_GROUP="jobs-sample"
LOCATION="northcentralus"
ENVIRONMENT="env-jobs-sample"
JOB_NAME="github-actions-runner-job"

# GitHub configuration
GITHUB_PAT="your_github_personal_access_token_here"
REPO_OWNER="your-github-username"
REPO_NAME="your-repository-name"

# Container configuration
CONTAINER_IMAGE_NAME="github-actions-runner:1.0"
CONTAINER_REGISTRY_NAME="your-unique-registry-name"

# Identity configuration
IDENTITY="github-actions-runner-identity"

Enter fullscreen mode Exit fullscreen mode

⚠️ Security Note: Never commit your GitHub PAT to version control. Consider using Azure Key Vault for production environments.

Step 3: Create Azure Container Registry

Create a container registry to store your runner image.
I've installed all the required:

az acr create \
    --name "$CONTAINER_REGISTRY_NAME" \
    --resource-group "$RESOURCE_GROUP" \
    --location "$LOCATION" \
    --sku Basic

Enter fullscreen mode Exit fullscreen mode

Step 4: Configure Registry Authentication

Enable authentication-as-ARM for your container registry:

az acr config authentication-as-arm show --registry "$CONTAINER_REGISTRY_NAME"

Enter fullscreen mode Exit fullscreen mode

Step 5: Build the Runner Container Image

Build your GitHub Actions runner container image from the repository:

az acr build \
    --registry "$CONTAINER_REGISTRY_NAME" \
    --image github-actions-runner:2.1 \
    --file "Dockerfile.github" \
    "https://github.com/TuanLikeminds/container-apps-ci-cd-runner-tutorial.git"

Enter fullscreen mode Exit fullscreen mode

I've customized the Dockerfile used to build the github-actions running container image that can be found at https://github.com/Azure-Samples/container-apps-ci-cd-runner-tutorial/blob/main/Dockerfile.github

Pre-installed the Azure-CLI, Kubectl, and Helm binaries required by the actions runner docker container by installing Azure CLI and Kubectl. See below

FROM ghcr.io/actions/actions-runner:2.304.0
# for latest release, see https://github.com/actions/runner/releases

USER root

# install curl and jq



RUN apt-get update && apt-get install -y curl jq apt-transport-https ca-certificates gnupg lsb-release git && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# install Azure CLI
# https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-linux?view=azure-cli-latest&pivots=apt#option-1-install-with-one-command
RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash && curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
RUN az aks install-cli

RUN  curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \
    chmod +x ./kubectl && \
    mv ./kubectl /usr/local/bin/kubectl


COPY github-actions-runner/entrypoint.sh ./entrypoint.sh
RUN chmod +x ./entrypoint.sh

USER runner

ENTRYPOINT ["./entrypoint.sh"]

Enter fullscreen mode Exit fullscreen mode

Step 6: Create the Container App Job

Create the container app job with auto-scaling configuration:

az containerapp job create \
    --name "$JOB_NAME" \
    --resource-group "$RESOURCE_GROUP" \
    --environment "$ENVIRONMENT" \
    --trigger-type Event \
    --replica-timeout 1800 \
    --replica-retry-limit 0 \
    --replica-completion-count 1 \
    --parallelism 1 \
    --image "$CONTAINER_REGISTRY_NAME.azurecr.io/$CONTAINER_IMAGE_NAME" \
    --min-executions 0 \
    --max-executions 10 \
    --polling-interval 30 \
    --scale-rule-name "github-runner" \
    --scale-rule-type "github-runner" \
    --scale-rule-metadata "githubAPIURL=https://api.github.com" "owner=$REPO_OWNER" "runnerScope=repo" "repos=$REPO_NAME" "targetWorkflowQueueLength=1" \
    --scale-rule-auth "personalAccessToken=personal-access-token" \
    --cpu "2.0" \
    --memory "4Gi" \
    --secrets "personal-access-token=$GITHUB_PAT" \
    --env-vars "GITHUB_PAT=secretref:personal-access-token" "GH_URL=https://github.com/$REPO_OWNER/$REPO_NAME" \
    --registry-server "$CONTAINER_REGISTRY_NAME.azurecr.io" \
    --mi-user-assigned "$IDENTITY_ID" \
    --registry-identity "$IDENTITY_ID"

Enter fullscreen mode Exit fullscreen mode

Key Configuration Parameters Explained

  • replica-timeout: Maximum time (30 minutes) a job can run
  • min-executions/max-executions: Scaling limits (0-10 concurrent runners)
  • polling-interval: How often to check for new workflows (30 seconds)
  • targetWorkflowQueueLength: Triggers scaling when queue length reaches 1
  • cpu/memory: Resource allocation per runner instance

Step 7: Configure Role Assignments

Set up the necessary permissions for your identities. You'll need to replace the GUIDs with your actual identity IDs:

For Container Registry Access

# Replace with your managed identity ID
IDENTITY_ID="your-managed-identity-id"

# ACR Pull permissions
az role assignment create \
    --assignee "$IDENTITY_ID" \
    --role AcrPull \
    --scope "/subscriptions/your-subscription-id/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerRegistry/registries/$CONTAINER_REGISTRY_NAME"

# ACR Push permissions (if needed for building images)
az role assignment create \
    --assignee "$IDENTITY_ID" \
    --role AcrPush \
    --scope "/subscriptions/your-subscription-id/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerRegistry/registries/$CONTAINER_REGISTRY_NAME"

Enter fullscreen mode Exit fullscreen mode

For AKS Access (if deploying to Kubernetes)

az role assignment create \
    --assignee "$IDENTITY_ID" \
    --role "Azure Kubernetes Service RBAC Admin" \
    --scope "/subscriptions/your-subscription-id/resourceGroups/your-aks-resource-group/providers/Microsoft.ContainerService/managedClusters/your-aks-cluster-name"

Enter fullscreen mode Exit fullscreen mode

Step 8: Verify Setup

After completing the setup, verify your configuration:

  1. Check Container App Job Status:

    az containerapp job show --name "$JOB_NAME" --resource-group "$RESOURCE_GROUP"
    
    
  2. Monitor Logs:

    az containerapp job logs show --name "$JOB_NAME" --resource-group "$RESOURCE_GROUP"
    
    
  3. GitHub Repository: Check your repository's Actions tab for the new self-hosted runner.

How It Works

  1. Trigger: When a workflow is queued in your GitHub repository
  2. Scale: Container Apps detects the queue and starts a new runner instance
  3. Execute: The runner picks up and executes the workflow
  4. Clean Up: After completion, the runner instance is terminated
  5. Scale Down: When no workflows are queued, the system scales to zero

Troubleshooting

Common Issues

  1. Runner Not Appearing in GitHub:
    • Verify your GitHub PAT has correct permissions
    • Check the REPO_OWNER and REPO_NAME variables
    • Review container logs for authentication errors
  2. Scaling Issues:
    • Ensure polling-interval and scale rule metadata are correctly configured
    • Check if targetWorkflowQueueLength matches your needs
  3. Container Registry Access:
    • Verify role assignments for your managed identity
    • Ensure the container image exists in your registry

Useful Commands

# View job executions
az containerapp job execution list --name "$JOB_NAME" --resource-group "$RESOURCE_GROUP"

# Get detailed logs
az containerapp job logs show --name "$JOB_NAME" --resource-group "$RESOURCE_GROUP" --follow

# Update job configuration
az containerapp job update --name "$JOB_NAME" --resource-group "$RESOURCE_GROUP" --image "new-image:tag"

Enter fullscreen mode Exit fullscreen mode

Security Best Practices

  1. Use Managed Identities: Avoid storing credentials in environment variables
  2. Least Privilege: Grant only necessary permissions to your identities
  3. Regular Token Rotation: Rotate GitHub PATs regularly
  4. Network Security: Consider using private endpoints for production
  5. Resource Limits: Set appropriate CPU and memory limits

Cost Optimization

  • Scale to Zero: Runners automatically scale down when not in use
  • Resource Sizing: Adjust CPU/memory based on your workflow requirements
  • Regional Deployment: Choose regions close to your development team
  • Monitoring: Use Azure Monitor to track usage and optimize

Next Steps

  • Set up monitoring and alerting for your runners
  • Configure network policies for enhanced security
  • Implement GitOps workflows using your new runners
  • Consider setting up multiple runner pools for different workload types

Note: Remember to replace all placeholder values (subscription IDs, resource names, GitHub information) with your actual configuration before running the commands.

Top comments (0)